The go-to resource for learning PHP, Laravel, Symfony, and your dependencies.

Automated Dependency Updates with Dependabot


In the Serengeti, elephant herds remember water sources across hundreds of miles, knowledge passed from matriarch to younger members over decades. When drought comes, this accumulated memory means survival—the herd knows which distant waterholes remain when others dry up.

Similarly, when maintaining a PHP application with multiple Composer dependencies, you need to know which packages have updates available—security patches, bug fixes, or new features. Manual tracking is like wandering without a map; you might find water eventually, but you’ll waste precious time and energy. One may wonder: what if your herd could automatically notify you of every water source that becomes available?

That’s precisely what Dependabot does for your PHP projects. It’s an automated system that watches your composer.json file and creates pull requests whenever updates are available—turning dependency maintenance from an uncertain chore into a predictable, manageable workflow.

Strictly speaking, Dependabot is a GitHub-native service, not something you install. The configuration lives in .github/dependabot.yml in your repository, deeply integrated with GitHub’s ecosystem. Though we’ll focus on Dependabot in this guide, alternatives like Renovate (for GitLab or Azure DevOps) and custom scripts exist—but for GitHub users, Dependabot offers the simplest path with zero external dependencies.

Prerequisites

Before we begin, let’s ensure you have the foundation in place. Dependabot for PHP requires:

  • A GitHub repository: Dependabot is a GitHub-only service; it won’t work on GitLab or Bitbucket without significant adaptation.
  • PHP project with composer.json: Your project must use Composer and have a composer.json file at the repository root or a known subdirectory.
  • GitHub Actions or CI (recommended): While not strictly required, a CI pipeline to test Dependabot’s pull requests is strongly recommended for safety.
  • Basic familiarity with YAML: The configuration file uses YAML syntax, which is whitespace-sensitive.
  • Push access to the default branch: Dependabot only monitors your default branch (typically main or master) by default.

If any of these are missing, we’ll address them as we go. Though you can enable Dependabot immediately, you should set up CI first to avoid merging untested updates.

Understanding the Value: Why Automate?

Before we dive into configuration, let’s examine why Dependabot matters—because the benefits extend well beyond convenience.

Enhanced Security

The most compelling reason is security. When a vulnerability is discovered—say CVE-2024-1234 in a JSON parser—a patch is often released within days. Of course, you could manually track security advisories for every dependency, but that’s impractical for any project with more than a handful of packages.

Dependabot integrates with GitHub’s Security Advisories database. When a match is found, you get a pull request within hours, often with automated release notes. For example, if your project uses GuzzleHTTP 7.4.0 and a security update to 7.4.5 is released, Dependabot creates a PR titled “Bump guzzlehttp/guzzle from 7.4.0 to 7.4.5.” You review, test, merge—and you’re protected. The alternative is discovering the vulnerability during an audit when you’re under pressure to patch quickly.

Increased Stability

Here’s a scenario many teams face: a project hasn’t updated dependencies in two years. When you finally try, you encounter a cascade of breaking changes across dozens of packages. The upgrade that should take hours becomes weeks of debugging. This is what’s often called “dependency hell.”

The solution is incremental updates. By updating small versions as they’re released—say, moving from symfony/console 6.3.3 to 6.3.4—you isolate changes. If something breaks, you know exactly which package caused it. This is far less risky than jumping from Laravel 8 to Laravel 11 in one go. Dependabot enforces this discipline automatically.

Tip: The strategy mirrors “continuous integration” applied to dependencies—small, frequent updates reduce risk significantly.

Developer Efficiency

Let’s quantify this. Suppose you manage a medium application with 20 Composer dependencies. On average, each gets updated every 3-4 weeks. That’s 20 manual checks, 20 git branches, 20 pull requests, and 20 merge cycles per month—potentially 40-80 hours of developer time.

With Dependabot, PRs appear automatically. Your team reviews them during regular workflows, perhaps even using GitHub’s “Auto-merge” feature after CI passes. You still need to review, but the difference is this: manual updates happen when someone remembers, often in a rush. Automated updates follow a predictable schedule and integrate with your existing code review process.

Ultimately, though, the efficiency gains aren’t just about hours saved—they’re about mental overhead. When dependency maintenance is automated, your team can focus on features.

How Dependabot Works

Let’s examine the mechanics. Dependabot runs on GitHub’s servers, scanning your default branch periodically. When it finds that a dependency constraint in your composer.json (e.g., ^7.0) could accept a newer version, Dependabot:

  1. Checks if that newer version passes any configured allow/ignore rules
  2. Creates a separate branch with the updated composer.json and composer.lock
  3. Opens a pull request with a standardized title like “Bump guzzlehttp/guzzle from 7.4.0 to 7.4.5”
  4. Populates the PR body with release notes and changelog information when available
  5. Waits for your review, testing, and merge

For security updates, this process happens immediately—bypassing your normal schedule—because you don’t want to wait 24 hours when a critical vulnerability is being actively exploited.

Of course, Dependabot doesn’t merge automatically by default. That’s by design: you retain control over which updates enter your codebase. Though you can enable auto-merge for security updates or use GitHub’s “Auto-merge” button manually, most teams review each PR at least briefly.

Setting Up Dependabot: A Complete Walkthrough

Now let’s walk through the actual setup. We’ll assume you have a Laravel project with this composer.json in the repository root:

{
    "require": {
        "php": "^8.1",
        "laravel/framework": "^10.0",
        "guzzlehttp/guzzle": "^7.0"
    }
}

Here’s what you do, step by step:

Step 1: Create the Configuration Directory

If you haven’t already, create the .github directory at your repository root:

$ mkdir -p .github

We use -p so the command succeeds even if .github already exists. If you see an error about the directory already existing, that’s fine—the command will exit successfully anyway.

Step 2: Create the Dependabot Configuration

Create .github/dependabot.yml with your preferred editor, or use this shell command:

$ cat > .github/dependabot.yml << 'EOF'
version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "daily"
EOF

Important: YAML is whitespace-sensitive—use spaces, not tabs. The above configuration is the minimal valid setup. Let’s break down what each field does:

  • version: 2 — Use configuration schema version 2 (current as of 2024). Version 2 supports all major features; avoid version 1.
  • updates: — Begins the list of update configurations. You can have multiple entries (e.g., one for Composer, another for npm in a monorepo).
  • package-ecosystem: "composer" — Identifies Composer as the package manager. Must be exactly "composer" (case-sensitive).
  • directory: "/" — Where to look for composer.json. Use "/" for repository root, or specify a subdirectory like "backend" if your Composer files live there.
  • schedule.interval: "daily" — How often Dependabot checks for updates. Options: "daily", "weekly", or "monthly". Daily is generally recommended for security-sensitive projects.

Step 3: Commit and Push

$ git add .github/dependabot.yml
$ git commit -m "Add Dependabot configuration for Composer"
$ git push origin main

Of course, replace main with your default branch name if it’s different—master is still common in some older projects. Push to the default branch specifically, because Dependabot only monitors the default branch by default.

Step 4: Verify Activation

After pushing, go to your repository on GitHub:

  1. Click the “Insights” tab
  2. In the left sidebar, click “Dependency graph”
  3. Scroll down to the “Dependabot” section

You should see Dependabot listed as active. Within a few hours (often 1-3 hours), you’ll receive your first pull request if any dependencies are outdated. If you don’t see any PRs after 24 hours, check that:

  • The dependabot.yml file is at the repository root (not in a submodule)
  • You pushed to the default branch
  • Your composer.json file is in the directory you specified
  • Your repository is on GitHub (not GitHub Enterprise Server, which has different configuration requirements)

Tip: Dependabot doesn’t activate instantly. It typically checks once per day according to GitHub’s schedule. If you need an immediate check, you can trigger Dependabot manually by pushing a trivial commit.

Configuration Options

The minimal configuration works, but Dependabot offers many options to fine-tune its behavior. Let’s examine the most useful ones.

Version Constraints and Allow Rules

By default, Dependabot respects the version constraints in your composer.json. If you specify ^7.0, Dependabot creates PRs for any 7.x version that satisfies the constraint. You can further control this with allow and ignore rules:

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "daily"
    allow:
      - dependency-type: "direct"
        # Allow minor updates for direct dependencies
      - package-name: "laravel/framework"
        dependency-type: "direct"
        # Laravel follows semver strictly, so minor updates are safe
    ignore:
      - dependency-name: "some/unstable-package"
        versions: ["<3.0"]
        # Skip versions before 3.0 due to breaking changes

Key fields:

  • allow — Explicitly allows updates matching the criteria. You can filter by dependency-type ("direct", "indirect"), package-name, or both.
  • ignore — Explicitly skips certain packages or versions. Useful for packages with known issues or breaking changes.

Though these rules seem similar, allow is generally more permissive and ignore more restrictive. Use allow when you want to limit updates to specific packages or types; use ignore when you need to exclude specific problematic cases.

Grouping Updates

If your project has many dependencies, you might receive dozens of PRs per week. Grouping lets you bundle related packages into a single PR:

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    groups:
      testing:
        patterns:
          - "phpunit/phpunit"
          - "mockery/mockery"
          - "phpunit/php-code-coverage"
      framework:
        patterns:
          - "laravel/framework"
          - "laravel/tinker"
    schedule:
      interval: "daily"

Now updates to PHPUnit-related packages appear in one PR titled “Bump dependencies in the testing group,” while Laravel updates appear separately. Grouping reduces noise, though it means each PR contains more changes to test.

Important: Groups apply to version updates only—not security updates. Security updates always create individual PRs for immediate action.

Open Pull Request Limits

To avoid flooding your repository with PRs, use open-pull-requests-limit:

updates:
  - package-ecosystem: "composer"
    directory: "/"
    open-pull-requests-limit: 10

This limits Dependabot to 10 open PRs at once. Once you merge or close some, Dependabot opens new ones for pending updates. Set this based on your team’s capacity—5-10 is typical for small teams, 20-30 for larger teams with dedicated review resources.

Commit Message Customization

Control the format of Dependabot’s commit messages:

commit-message:
  prefix: "chore"
  include: "scope"

This generates: chore: update dependency foo/bar to v1.2.3. Options:

  • prefix — Prefix for all commit messages (default: empty). Common choices: "chore", "deps", "update".
  • include — What to include: "scope" (package name), "message" (release notes summary), both, or empty.

Security Updates Configuration

Security updates are always high priority, but you can control their behavior:

updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "daily"
    security-updates:
      enabled: true  # Default: true

You generally want security updates enabled—they’re the whole point of Dependabot’s speed. The enabled: false option is only useful if you handle security updates through another channel.

Verification and Testing

How do we know Dependabot is working correctly? Let’s establish verification procedures.

Verifying Dependabot Activation

After pushing your configuration file, confirm activation:

  1. Go to Repository SettingsPages → Ensure GitHub Pages is disabled (it can interfere with some Dependabot features)
  2. Go to InsightsDependency graph → Verify Dependabot appears under “Dependency update tools”
  3. Check that the status says “Active” and shows your configured schedule

If Dependabot shows as inactive:

  • Verify the file path: .github/dependabot.yml (not .dependabot/config.yml or dependabot.yml in the root)
  • Confirm you pushed to the default branch (Dependabot doesn’t monitor feature branches by default)
  • Check for YAML syntax errors (use an online validator or yamllint)
  • Ensure your repository isn’t archived or disabled

Testing with a Manual Dependency Update

To verify Dependabot will actually create PRs, we can test it with a simple dependency bump:

  1. Check your current dependencies:

    $ composer show --outdated

    This lists all packages with newer versions available. Note one package with an available update.

  2. Temporarily relax the version constraint in your composer.json to accept that newer version. For example, if guzzlehttp/guzzle is constrained to ^7.0 and version 7.8.0 is available (but your constraint only goes up to 7.4.x), change it to ^7.0 || ^8.0 temporarily:

    {
        "require": {
            "guzzlehttp/guzzle": "^7.0 || ^8.0"
        }
    }
  3. Commit and push this change to your default branch:

    $ git add composer.json
    $ git commit -m "Test: relax guzzle constraint"
    $ git push origin main
  4. Wait for Dependabot. Within a few hours, you should see a PR titled “Bump guzzlehttp/guzzle from X.Y.Z to A.B.C.”

  5. Verify the PR:

    • The PR should come from a dependabot user
    • The branch should be named dependabot/composer/guzzlehttp-guzzle-X.Y.Z
    • The PR should include updated composer.lock
    • The PR body should contain release notes if available
  6. Clean up after testing: either merge the PR if you want the update, or close it and revert your composer.json changes.

If no PR appears after 24 hours, review your Dependabot logs in the repository’s SettingsSecurity & analysisDependabot section. Error messages there often point to misconfiguration.

Testing Your CI Pipeline

Dependabot PRs trigger your CI configuration just like any other PR. Verify this works:

  1. Create a test Dependabot PR (using the method above, or wait for a real one)
  2. Go to the Actions tab (if using GitHub Actions)
  3. You should see a workflow run for that PR
  4. Confirm it completes successfully (or at least runs)

If CI doesn’t trigger:

  • Check that your workflow file has pull_request or pull_request_target triggers
  • Verify Dependabot PRs aren’t excluded by if: conditions
  • Ensure your CI service is connected to the repository

Tip: Enable GitHub’s “Auto-merge” feature for Dependabot PRs that pass CI. This requires branch protection rules requiring status checks—set those up first.

Common Issues and Troubleshooting

Even with correct configuration, you might encounter issues. Here are solutions to common problems.

Dependabot Isn’t Creating PRs

Symptom: You’ve pushed .github/dependabot.yml but no PRs appear after 24+ hours.

Possible causes and solutions:

  1. File location or naming is wrong

    • Ensure the file is exactly .github/dependabot.yml (not .dependabot/config.yml, not dependabot.yml in the root)
    • The .github directory must be at the repository root
  2. Not pushing to the default branch

    • Dependabot only monitors the default branch (usually main or master)
    • Pushing to a feature branch won’t trigger updates
  3. YAML syntax error

    • Validate your YAML with yamllint or an online validator
    • Common issues: tabs instead of spaces, incorrect indentation, missing colons
  4. No outdated dependencies

    • Dependabot only creates PRs when updates are actually available
    • Run composer show --outdated to confirm there are updates
    • If all dependencies are already current, Dependabot won’t open PRs
  5. Directory misconfiguration

    • If your composer.json is in a subdirectory, set directory: "subdir" accurately
    • The directory path is relative to the repository root
  6. GitHub Enterprise Server

    • If you’re using GitHub Enterprise Server (self-hosted), Dependabot configuration differs
    • Enterprise Server uses .dependabot/config.yml (version 1 format) in some versions
    • Consult your Enterprise Server documentation

Dependabot PRs Fail CI

Symptom: Dependabot PRs consistently fail your CI tests.

Solutions:

  1. Run the update locally first: Pull the Dependabot branch and run your full test suite:

    $ git checkout dependabot/composer/package-name-version
    $ composer install
    $ ./vendor/bin/phpunit

    Identify whether the failure is due to an actual breaking change or a flaky test/environment issue.

  2. Check for PHP version conflicts: Dependabot doesn’t account for your PHP version constraint. If a package update requires a higher PHP version than you have in composer.json, the update will fail. Adjust your PHP constraint or ignore that package:

    ignore:
      - dependency-name: "some/package"
        versions: ["*"]  # Or specific version range
  3. Environment differences: Your CI environment might use different PHP, Composer, or extension versions. Ensure consistency between local development and CI.

  4. Update the dependency constraint: Sometimes an update fails because it’s actually a major version violating your constraint. Check the PR title—if it says “Bump from 1.x to 2.x” but you only allowed ^1.0, the update may not be compatible. Adjust your constraint in composer.json if you’re ready for the major version.

Merge Conflicts Occur Frequently

Symptom: Dependabot PRs constantly have merge conflicts with other changes.

Solution: This happens when you modify composer.json or composer.lock directly without merging Dependabot PRs first. The workflow should be:

  1. Never edit composer.lock manually—always use composer update to regenerate it
  2. Merge or rebase Dependabot PRs promptly—let them sit too long and conflicts accumulate
  3. Use composer require to add or update packages, not manual editing
  4. If you must edit composer.json directly, immediately merge any pending Dependabot PRs or close them and let Dependabot recreate them

Advanced solution: Use Dependabot’s grouping feature to reduce the number of concurrent PRs, making conflicts less likely.

Dependabot Isn’t Detecting Security Updates

Symptom: You know a CVE affects one of your dependencies, but Dependabot hasn’t created a security PR.

Possible causes:

  1. The vulnerability isn’t in GitHub’s Security Advisory database yet. GitHub only creates security updates for CVEs they’ve documented and mapped to your dependencies.
  2. The fix isn’t released yet. Sometimes the vulnerability is known but the patch isn’t available.
  3. Your dependency is too old or too new. GitHub might not have a mapping between the vulnerable version range and the patched version for your specific constraint.
  4. The package isn’t in the advisory database—some lesser-known packages aren’t tracked.

What you can do:

  • Manually check for updates with composer outdated
  • Check GitHub’s Security Advisories directly: https://github.com/advisories (search for your package)
  • Run composer audit locally to detect known vulnerabilities
  • If a fix exists but Dependabot isn’t creating a PR, you can create the PR manually or adjust your constraints with allow rules

Too Many PRs Overwhelming the Team

Symptom: Dependabot creates 20-30 PRs per week and the team can’t keep up.

Solutions:

  1. Increase open-pull-requests-limit to a lower number (try 5):

    open-pull-requests-limit: 5

    This prevents new PRs from opening until you merge some. Dependabot will queue them.

  2. Use grouping to bundle related packages:

    groups:
      framework:
        patterns:
          - "laravel/framework"
          - "laravel/tinker"
          - "laravel/sanctum"
  3. Adjust your version constraints in composer.json to be more conservative. ^1.0 accepts 1.x only; ~1.0 is even more conservative. This reduces the frequency of updates.

  4. Enable auto-merge for patch updates (with caution):

    • In GitHub, go to your repository’s SettingsCode security and analysisDependabotEnable auto-merge for Dependabot updates
    • Choose to auto-merge only patch updates, or all updates that pass CI
    • Warning: Auto-merging without human review risks introducing regressions. Only do this if your test suite is very comprehensive and you’re comfortable with automated merges.
  5. Switch to weekly or monthly schedule if daily is too frequent:

    schedule:
      interval: "weekly"
      day: "monday"  # Optional: specific day

Dependabot Updates a Package That Breaks Tests

Symptom: A Dependabot PR causes test failures.

This is expected and normal. Dependabot updates without knowing your application’s specific behavior. Here’s the workflow:

  1. Review the PR—look at the changelog to understand what changed
  2. Run the tests locally on the Dependabot branch
  3. Investigate failures:
    • Are they legitimate breaking changes that require code changes?
    • Are they flaky tests unrelated to the update?
    • Are they environment issues (PHP version, extensions)?
  4. Decide:
    • If legitimate: fix your code, push to the PR branch, merge when tests pass
    • If flaky/environment: investigate separately, but don’t block necessary updates indefinitely
    • If the package introduced an unacceptable breaking change without constraint bump: you may need to adjust your composer.json constraint or find an alternative

Important: Don’t disable Dependabot because of occasional breaking changes. That’s the point of having tests—to catch issues before they reach production. The alternative (manual updates) would have introduced the same breaking change, just without the safety net of PR-based review.

Best Practices Summary

Let’s consolidate the key recommendations:

  1. Have a solid CI pipeline before enabling Dependabot. Without tests, you’re just guessing whether updates are safe.
  2. Start with daily schedule, weekly if daily is too noisy. Monthly is rarely sufficient for security.
  3. Merge or close Dependabot PRs promptly—within a week if possible—to avoid accumulating conflicts.
  4. Use open-pull-requests-limit to prevent overwhelming your team.
  5. Group related packages where breaking changes might cascade (e.g., all Laravel components, all PHPUnit tools).
  6. Enable auto-merge only for security updates (with CI pass requirement), not all updates—unless you have exceptional test coverage and risk tolerance.
  7. Monitor the Security tab in GitHub for unresolved Dependabot alerts.
  8. Tune version constraints in composer.json to match your upgrade tolerance. ^ is permissive; ~ is conservative.
  9. Set up branch protection requiring CI passes before merge, and at least one reviewer for major version updates.
  10. Keep your PHP constraint up to date—Dependabot won’t update PHP itself, and some package updates require newer PHP versions.

Conclusion

Dependabot isn’t magic—it automates the mechanical parts (version checking and PR creation) but you still need to review and test. The value lies in transforming dependency maintenance from a sporadic, forgotten chore into a predictable, manageable workflow.

We’ve covered a lot: the security imperative, stability benefits, efficiency gains, configuration options, verification steps, and troubleshooting. You now have everything you need to enable Dependabot on your PHP projects.

The minimal configuration—seven lines of YAML—handles the core functionality:

version: 2
updates:
  - package-ecosystem: "composer"
    directory: "/"
    schedule:
      interval: "daily"

Put that in .github/dependabot.yml, push to your default branch, and let the automation begin.

Of course, this is just the start. As you grow comfortable with Dependabot, explore its advanced features: grouping strategies, commit message customization, fine-grained allow/ignore rules, and integration with GitHub’s security features. The official documentation at github.com/features/security covers every option in detail.

What matters most is this: dependency maintenance is no longer optional. Security vulnerabilities appear regularly—sometimes weekly—and staying current is baseline hygiene. By enabling Dependabot today, you’re not just checking a box; you’re choosing sustainable maintenance over accumulating technical debt.

Your Serengeti herd now has its automatic scouts. Trust them to find the water, but still bring your judgment to decide which sources to use.

Go add that .github/dependabot.yml file. Let’s keep our dependencies current, our applications secure, and our teams focused on what matters.

Sponsored by Durable Programming

Need help with your PHP application? Durable Programming specializes in maintaining, upgrading, and securing PHP applications.

Hire Durable Programming