Skip to main content
友田 陽大
Dependabot & dependency automation
Dependabot
モノレポ
Turborepo
依存関係管理
GitHub Actions
DevSecOps

Running a monorepo with Dependabot: a design with directories and groups that doesn't break Turborepo / pnpm workspaces

A design guide for operating Dependabot without breaking on a monorepo (Turborepo / pnpm/npm/yarn workspaces / Nx). Faithful to the official documentation (as of June 2026), it explains, in copy-paste real code: consolidating into one entry with directories globs, the relationship between workspaces and the lockfile, bundling PRs across directories with groups and group-by: dependency-name, per-package policies, and integration with CI that tests only the affected scope.

Published
Reading time
6 min read
Author
友田 陽大
Share

Drop Dependabot naïvely into a monorepo, and dozens of PRs line up on Monday morning — and then no one looks. The cause is clear: in a monorepo the update targets balloon to "package count × dependency count." But this can be solved by design. Consolidate with directories, bundle with groups, let things age with cooldown, and flow patch/minor through auto-merge — with these four points, dependency updates are quietly automated even in a huge monorepo.

This article focuses on monorepo-specific design as a topic of the Dependabot production-operations guide. It uses Turborepo / pnpm workspaces as the main subject, but the thinking is the same for npm/yarn workspaces and Nx.

Rules for this article: config keys and behavior are based on the GitHub official documentation (as of June 2026). directories (globs) and group-by are relatively new features. Always confirm the latest in the official options reference before production.


0. Why it breaks on a monorepo

通常リポジトリ:   1 つの package.json → 依存 N 個 → 最大 N 本のPR
モノレポ(素朴):  M 個の package.json → 依存 N 個ずつ → 最大 M×N 本のPR

With 5 in apps/, 10 in packages/, each holding dozens of dependencies, you easily reach 3-digit PR counts. Papering over this with "lower the frequency" then delays vulnerability response. The right answer is consolidation and bundling. Let's build it up in order.


1. Consolidate into one entry with directories globs

It used to be that you wrote as many updates entries as you had directories. Now you can consolidate into one entry with directories (plural) + globs.

version: 2
updates:
  - package-ecosystem: "npm"
    directories:
      - "/"            # ルート(ワークスペース定義・共有devDeps)
      - "/apps/*"      # 各アプリ
      - "/packages/**" # 共有パッケージ(再帰)
    schedule:
      interval: "weekly"

* is one level, ** is recursive. With this, adding a new app/package needs no config change (ETC: change stays in one place).


2. The relationship between workspaces and the lockfile (this is the crux)

A monorepo's dependency resolution is determined by the location of the lockfile.

  • pnpm workspaces: pnpm-lock.yaml is a single file at the repository root. All workspaces' dependencies are resolved here.
  • npm / yarn workspaces: similarly, a single lockfile at the root (package-lock.json / yarn.lock).

So in many monorepos, you can first update all workspaces' external dependencies with just a single root (/) entry. Each apps/*'s package.json declares "which external dependencies it uses," but the source of truth for resolution is the root lockfile.

# pnpm/npm/yarn workspaces:まずはルート1エントリで十分なことが多い
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"

Expand with directories only when you first want to separate different update policies (frequency, groups, ignore) per individual app. YAGNI: not splitting per app from the start, but splitting once you need to, is the trick to keeping operations light.


3. Bundle PRs with groups

Even after consolidating, without bundling you get as many PRs as dependencies. Group them into meaningful units with groups.

updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "weekly"
    groups:
      types:
        patterns: ["@types/*"]
      testing:
        patterns: ["vitest", "@testing-library/*", "playwright", "msw"]
      linting:
        patterns: ["eslint*", "prettier", "@typescript-eslint/*"]
      # 上記以外の minor/patch を1本に(major は個別PRのまま)
      minor-and-patch:
        update-types: ["minor", "patch"]
        exclude-patterns: ["@types/*", "vitest", "eslint*"]

This consolidates into the 4–5 PRs of "types," "testing," "lint," and "other minor/patch," dramatically reducing the review target. For all groups options, see the complete configuration guide.


4. Consolidate across directories with group-by: dependency-name

When you've split directories per app, it's self-defeating to have "every app's react update turned into separate PRs." With group-by: dependency-name, bundle the same dependency across directories.

  - package-ecosystem: "npm"
    directories: ["/apps/*"]
    schedule:
      interval: "weekly"
    groups:
      react-across-apps:
        patterns: ["react", "react-dom", "next"]
        group-by: "dependency-name"   # 全 app の react/next 更新を1PRに

It's a setting that pairs well with the operation of aligning framework versions across the whole monorepo (preventing version skew).


5. Internal packages are out of scope (so it's easy)

In a monorepo you reference internal packages like "@acme/ui": "workspace:*". This workspace:* reference is not an update target for Dependabot — because the version of an internal package is something the monorepo itself manages.

So Dependabot focuses solely on tracking external dependencies. No need to worry "I have to ignore internal dependencies." Consistency among internal packages is the job of other tools like Turborepo/Nx's build graph or changesets (SRP: don't mix responsibilities).


6. Per-package policy (updates proportional to risk)

Not everything needs to be updated with the same intensity. The closer to production, the more cautious the assignment.

updates:
  # アプリ:積極的に追従(minor/patch は groups でまとめ auto-merge 前提)
  - package-ecosystem: "npm"
    directories: ["/apps/*"]
    schedule:
      interval: "weekly"
    groups:
      app-minor-patch:
        update-types: ["minor", "patch"]
    ignore:
      - dependency-name: "*"
        update-types: ["version-update:semver-major"]   # major は人間が棚卸し

  # 共有ライブラリ:保守的(月次・cooldown 長め)
  - package-ecosystem: "npm"
    directories: ["/packages/**"]
    schedule:
      interval: "monthly"
    cooldown:
      default-days: 14

A breaking change in a shared library ripples to all apps. So lower the frequency for libs and make cooldown longer. Have apps track fast and small. This division solves the stability-vs-freshness trade-off in a grounded form.


7. CI and auto-merge: run only the affected scope

Full-testing all packages on a monorepo wastes CI time (= Actions billing) on every Dependabot PR. Test only the affected packages.

# .github/workflows/ci.yml(抜粋)— Turborepo の filter で影響範囲のみ
- run: pnpm install --frozen-lockfile
- run: pnpm turbo run lint test build --filter="...[origin/main]"

--filter="...[origin/main]" specifies running "only the packages affected by the change and their dependents." It becomes the minimum necessary test according to the Dependabot PR's diff — faster, cheaper, and more reliable.

On top of that, combine auto-merge to auto-merge grouped patch/minor conditioned on CI.

      - name: Auto-merge grouped patch/minor
        if: |
          steps.meta.outputs.dependency-group != '' &&
          steps.meta.outputs.update-type != 'version-update:semver-major'
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

8. Monorepo operations checklist

  • Start from a single root entry (the workspace lockfile is a single root file)
  • Expand with directories only when you need a per-app policy (YAGNI)
  • Bundle types/testing/lint/other minor-patch with groups
  • Align versions across apps with group-by: dependency-name
  • Shared libraries are monthly + longer cooldown, apps are weekly + aggressive tracking
  • CI runs only the affected scope (Turborepo/Nx filter) to minimize time and cost
  • Grouped patch/minor with auto-merge, major with human review

9. FAQ

Q. apps and packages all have package.json. Should I split the entries too? A. A single root entry is often enough first (the workspace lockfile is a single root file). Split with directories once you want to change frequency or groups individually.

Q. Every app's react is turned into separate PRs. A. If you've split directories, bundle the same dependency across directories with group-by: dependency-name. It also prevents version skew.

Q. Are internal packages (workspace:*) updated too? A. They aren't. Dependabot looks only at external dependencies. Internal consistency is the job of changesets or Turborepo/Nx.

Q. CI for all packages runs per PR — it's slow and expensive. A. Test only the affected scope with Turborepo/Nx's filter. Dependabot is free on standard runners, but CI itself consumes Actions minutes as usual.

Q. Is pnpm's pnpm-lock.yaml updated correctly? A. Yes. Dependabot resolves and updates the root lockfile. Always commit the lockfile.

友田

友田 陽大

Developer of a METI Minister's Award–winning product. With TypeScript + Python + AWS, I deliver SaaS, industry DX, and production-grade generative AI (RAG) end to end — from requirements to infrastructure and operations — single-handedly.

I can take on the implementation from this article as an engagement

Dependency auto-updates & supply-chain defense, from design to production

From enabling alerts/security/version updates, to dependabot.yml design (groups, cooldown, monorepo directories, private registries), safe auto-merge via GitHub Actions + fetch-metadata (auto for patch/minor, human review for major), severity-based-SLA vulnerability response, and observability of the open count. With experience wiring dependency updates into CI quality gates, I implement automation that doesn't flood you with PRs or pile up technical debt.

Available for both project-based (contract) and advisory engagements. Start with a free 30-minute consult.

Also worth reading