Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXVIII · Monorepo vs Multi-RepoArchitecture

Monorepo architecture — one repository, many projects

Advanced⏱ ~24 mingit

What you'll learn

  • Describe the physical shape of a monorepo and what it does to the working tree
  • Identify the build-system and ownership patterns that keep a monorepo tractable
  • Recognise the operational costs and the sparse-checkout patterns that mitigate them
  • Recall the published examples at Google, Meta, and Microsoft and the scale each operates at

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

A monorepo is a single repository that physically contains many projects. The trunk is one, the history is one, the branch protection is one - but the units of work are individual directories, individual ownership rows, and individual CI matrix entries. The whole model rests on the assumption that the team can solve the discoverability problem: given an engineer who works on directory services/payments/, how does the repository give them a fast working tree, a fast build, and a fast review, without paying the cost of cloning the entire history?

The physical shape

A monorepo is one .git directory, one HEAD, one set of branches, and one working tree at any moment. Under the root sits every directory the team owns: Terraform modules, Ansible roles, Kubernetes manifests, application code, generated clients, documentation. The unit of merge is the whole trunk; the unit of build is the directory a CI matrix entry declared; the unit of ownership is a row in CODEOWNERS that matches a path.

flowchart LR
    subgraph MR["Monorepo root"]
        T["terraform/"]
        A["ansible/"]
        K["kubernetes/"]
        S["services/"]
        D["docs/"]
    end
    MR --> CI[CI matrix per directory]
    MR --> CO[CODEOWNERS per path]
    MR --> BR[Single branch protection]

The benefit is that a single commit can touch every directory it needs to. The cost is that every commit is visible to every consumer of the trunk, and the working tree contains every directory even when the engineer only works on one.

What the build system has to do

A monorepo without a build system that understands the dependency graph is a monorepo that rebuilds the whole world on every commit. The published large-scale examples all share this property:

  • Google’s Piper / Bazel. Bazel is aware of the dependency graph between directories; a change to a leaf triggers only the targets that depend on it, and the build graph is the source of truth for what to run.
  • Meta’s Buck and the source-control record. The build graph is keyed by directory and target; CI runs the targets the changed directory owns, and the dependency graph is cached at the source-control level.
  • Microsoft’s internal build systems. The published whitepaper describes a single source tree shared across product divisions, with a build system that knows the entire dependency graph.

The pattern is the same: a monorepo needs a build graph as well as a source graph. Without the build graph, the monorepo is a CI tax. With the build graph, it is the unit of atomic cross-component change.

What the working tree has to do

Cloning a monorepo that contains a decade of history is expensive. The two mitigations are partial clone and sparse-checkout:

REPO_URL="https://github.com/example/monorepo.git"

git clone --filter=blob:none "$REPO_URL"
cd monorepo
git sparse-checkout init --cone
git sparse-checkout set "services/payments"
git ls-files | wc -l

The --filter=blob:none flag tells the server to omit blobs from the initial fetch; Git downloads them on demand as the checkout populates files. The sparse-checkout init --cone mode restricts the working tree to a single directory and its subtree; the git ls-files | wc -l line verifies that the working tree is actually the subset, not the whole repository.

What the published examples look like

The large-scale monorepo examples are not aspirational; they are operational:

  • Google. The Piper monorepo holds billions of lines, with a build graph (Bazel) that knows the dependency between every target. The stated motivation is the atomicity of cross-component refactors.
  • Meta. The source-control system is a monorepo with a build system (Buck) and a CI matrix keyed by directory. The stated motivation is the visibility of a single change across the entire codebase.
  • Microsoft. The Microsoft monorepo holds product source across many divisions, with a build system that understands the dependency graph. The stated motivation is the shared tooling and the shared review surface.

The trade-off published in all three is the same: the team pays the cost of the build graph and the sparse-checkout infrastructure in exchange for the atomicity of cross-component changes and the visibility of every commit.

Production discipline

  1. Pair every monorepo with a build graph. Without the graph, the CI matrix becomes a tax on every commit.
  2. Use sparse-checkout for working-tree performance, not for isolation. The isolation mechanism is CODEOWNERS plus branch protection.
  3. Verify the working tree with git ls-files after a sparse-checkout. A working tree that contains the whole repository is a working tree that is paying the full cost.
  4. Treat the trunk as sacred. A monorepo’s trunk is the single point of failure for the entire codebase; the branch protection on that trunk is the single point of control.

Cross-course references

  • Linux for Production Sysadmins - Part XXVI (RepoLayout) covers the filesystem analogue: a single root tree versus per-service trees, and the cost of the inode walk.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) covers the role-based layout of a monorepo Ansible repository, and the alignment between roles and owner rows.
  • Terraform for Production Sysadmins - Part IX (State) covers why Terraform state is the strongest argument for keeping Terraform in its own repository, even inside a monorepo.

Quiz

Knowledge check · 4 questions

  1. Q1. A team of eighty engineers adopts a monorepo. The CI matrix rebuilds the entire repository on every commit. What is the missing piece that makes a monorepo tractable at this scale?

  2. Q2. Sparse-checkout can be used to prevent an engineer from reading a directory they do not own.

  3. Q3. Name the two Git features that make a monorepo's working tree tractable at scale, and the two operational mechanisms that make the *rest* of the model work.

  4. Q4. Recommend a working-tree setup for an engineer onboarding onto a monorepo that contains 200,000 files, and verify the setup with `git ls-files`.

    An engineer joins a team whose monorepo holds 200,000 files across Terraform, Ansible, Kubernetes, and application code. The engineer will only work on `services/payments/` for the next quarter. The team is concerned about clone time, working-tree size, and auditability. Recommend the working-tree setup and a verification step.

Passing score: 75%. Answers are checked in this browser.