# 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: 2026-06-28
- Author: 友田 陽大
- Tags: Dependabot, モノレポ, Turborepo, 依存関係管理, GitHub Actions, DevSecOps
- URL: https://tomodahinata.com/en/blog/dependabot-monorepo-turborepo-pnpm-workspaces-directories-groups-guide
- Category: Dependabot & dependency automation
- Pillar guide: https://tomodahinata.com/en/blog/dependabot-production-guide

## Key points

- What breaks Dependabot on a monorepo is the explosion of 'PR count ≈ package count × dependency count.' The base design is to consolidate into one entry with directories globs (/apps/*, /packages/**) and bundle with groups.
- pnpm/npm/yarn workspaces have a single lockfile at the repository root. Dependabot resolves the root lockfile, so you can first update all workspaces' external dependencies with a single root entry.
- With groups, bundle 'type definitions,' 'testing,' and 'other minor/patch,' and with group-by: dependency-name consolidate the same dependency across multiple apps (e.g., react) into one PR. Focus review on major and substantive changes.
- Internal packages (workspace:* references) are out of Dependabot's update scope. So you can focus on tracking external dependencies, with no need to exclude internal dependencies via ignore.
- Use per-package policies: aggressive updates for apps, conservative for shared libraries. Let releases age with cooldown, and run CI that tests only affected packages (Turborepo's filter) plus auto-merge.

---

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](/blog/dependabot-production-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](https://docs.github.com/en/code-security/dependabot/working-with-dependabot/dependabot-options-reference) before production.

---

## 0. Why it breaks on a monorepo

```text
通常リポジトリ:   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.**

```yaml
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.

```yaml
# 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`.

```yaml
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](/blog/dependabot-yml-configuration-complete-guide#4-groups複数の更新を1つのprにまとめる).

---

## 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.**

```yaml
  - 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.

```yaml
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](/blog/dependabot-production-guide#3-どこで動くのか課金はどうなるか)) on every Dependabot PR. Test **only the affected packages.**

```yaml
# .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](/blog/dependabot-auto-merge-github-actions-automation-guide) to **auto-merge grouped patch/minor conditioned on CI.**

```yaml
      - 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.
