Skip to main content
RunBook Academy

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

Ownership and access control — CODEOWNERS at the repository and directory level

Advanced⏱ ~24 mingit

What you'll learn

  • Use CODEOWNERS at the repository level in a multi-repo and at the directory level in a monorepo
  • Reason about the blast radius of a misconfigured grant in each model
  • Recognise the failure modes of a CODEOWNERS row that grants access to the wrong team
  • Verify the ownership map with `git ls-files` and the CODEOWNERS file itself

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 CODEOWNERS file is a policy that maps paths to owners. The mapping is enforced by the Git hosting platform (GitHub, GitLab, Bitbucket) at pull-request review time, and the blast radius of the policy is the unit of the repository that the CODEOWNERS file belongs to. In a monorepo, that is the whole monorepo: a misconfigured row grants access to every directory the row matches. In a multi-repo, that is a single repository: the same misconfiguration grants access to a single repository.

The two shapes of CODEOWNERS

The CODEOWNERS file matches paths to owners. The path matching is the same in both models; the scope of the file is what differs:

  • Repository-level CODEOWNERS. A multi-repo fleet has one CODEOWNERS file per repository, and the file’s scope is the repository. A row that grants ownership to the wrong team grants it to the directories the repository contains.
  • Directory-level CODEOWNERS. A monorepo has one CODEOWNERS file at the repository root, and the file’s scope is the whole repository. A row that grants ownership to the wrong team grants it to the directories the row matches - which can be the whole monorepo if the row is /*.
flowchart LR
    subgraph MR["Monorepo"]
        A1[CODEOWNERS at root] --> A2[scope: whole monorepo]
    end
    subgraph MP["Multi-repo"]
        B1[CODEOWNERS per repo] --> B2[scope: single repo]
    end

The operational rule is the same in both models: the path match is the grant, and the blast radius is the unit of the file. The difference is the magnitude of the blast radius.

The blast radius of a misconfigured grant

A misconfigured CODEOWNERS row is a grant that gives the wrong team write access to a path the team should not own. The blast radius is the scope of the file:

  • Monorepo. A row /* @wrong-team grants the wrong team ownership over every directory the monorepo contains. The blast radius is the whole monorepo.
  • Multi-repo. A row /* @wrong-team in repo-payments-terraform grants the wrong team ownership over every directory in repo-payments-terraform. The blast radius is one repository.

The operational consequence is that high-blast-radius assets (network, IAM, secrets) belong in their own repository, not bundled with application code. The grant is scoped to the repository, and the misconfiguration is contained.

Verifying the ownership map

The ownership map is the file .github/CODEOWNERS (or the hosting-platform equivalent). The verification is to read the file and walk the path matches:

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

git clone --depth 1 "$REPO_URL"
cd infra-monorepo
git ls-files | grep -E 'CODEOWNERS'
cat .github/CODEOWNERS

The verification is two-fold: confirm that the CODEOWNERS file exists in the repository, and read the rows to confirm that the path matches are correct. The git ls-files line confirms the file is tracked; the cat line is the actual review.

Production discipline

  1. Treat CODEOWNERS as a security artifact, not a documentation artifact. The file is reviewed by the security team and audited quarterly.
  2. Use directory-scoped rows, not wildcard rows. A row /* @team is a global grant; the row should be /terraform/* @team, /kubernetes/* @team, etc.
  3. Scope every high-blast-radius asset to its own repository. Network, IAM, and secrets should not share a default branch with application code.
  4. Verify the ownership map with git ls-files and the CODEOWNERS file. A CODEOWNERS file that is not tracked is a CODEOWNERS file that does not exist.

Cross-course references

  • Linux for Production Sysadmins - Part XII (RepoSecurity) covers apt/dnf repository trust, which is the Linux-system-level analogue of Git repository ownership.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) covers the role-based layout of a single-tool Ansible repository, where roles are owned by directory rows.
  • Terraform for Production Sysadmins - Part IX (State) covers why Terraform state is the strongest argument for scoping every high-blast-radius asset to its own repository.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer writes a row `/* @platform-team` in the monorepo's CODEOWNERS file. The platform team owns only the `terraform/` and `kubernetes/` directories. What is the operational consequence of the wildcard row?

  2. Q2. Scoping a high-blast-radius asset (network, IAM, secrets) to its own repository reduces the blast radius of a misconfigured CODEOWNERS grant.

  3. Q3. Name the two shapes of CODEOWNERS (repository-level versus directory-level) and the rule that should drive which shape to use.

  4. Q4. Recommend the right CODEOWNERS shape for a monorepo that contains both application code and high-blast-radius network configuration, and verify the setup with the appropriate commands.

    A platform team maintains a monorepo that contains both application code and high-blast-radius network configuration (Terraform for VPCs, IAM bindings, secrets). The current CODEOWNERS file has a wildcard row `/* @platform-team`. The security team is concerned that the wildcard grants the platform team ownership over the application directories too.

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