Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXXXII · Protected BranchesPolicy

Protected branches and policy — branch protection as the enforcement layer

Advanced⏱ ~22 mingit

What you'll learn

  • Identify branch protection as the place where team policy becomes forge-enforceable
  • Map the relationship between branch protection, CODEOWNERS, signed commits, and audit logs
  • Codify the branch-protection configuration as a version-controlled artefact
  • Recognise the limits of the layer and the compensating controls that close the gaps

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.

Branch protection is the place where team policy becomes forge- enforceable. Everything that the team has decided should be true of its default branch - “every change is reviewed”, “two approvals minimum”, “the CI must be green”, “an admin cannot silently push” - is rendered into a rule that the forge applies on every push and merge. The rule set is the policy, executable. This closing lesson ties the rule set to the other layers in the governance stack and walks the discipline of codifying it.

Branch protection as the enforcement layer

A team policy is a statement about how changes should be made. A branch protection rule is the same statement, expressed in a form the forge can enforce. The map between the two is direct:

flowchart LR
    A[Team policy statement] --> B[Rule configuration]
    B --> C[Forge evaluates at merge time]
    C --> D{Merge candidate satisfies rule?}
    D -->|yes| E[Merge accepted]
    D -->|no| F[Merge refused]
Team policyProtection rule
“Every change is reviewed”Require a pull request before merging
“Two approvals minimum”Required approving reviews: 2
“Approvals must be current”Dismiss stale pull request approvals
“An owner must approve on owned paths”Require review from Code Owners
“CI must be green”Require status checks to pass before merge
“Checks must be current”Require branches to be up to date
“Admins cannot bypass”Do not allow bypassing by administrators
“Specific people can bypass in incidents”Allow specified actors to bypass
“History is not rewritten”Do not allow force pushes
“The branch is not deleted”Do not allow deletions

This mapping is not optional. Every team-policy claim must have a rule. A claim without a rule is a claim the forge cannot enforce, which means the claim is opt-in, which means the claim is not a control.

The relationship with CODEOWNERS

Branch protection and CODEOWNERS are layered: CODEOWNERS routes the review, branch protection enforces that the review happened.

A pull request touches files. CODEOWNERS asks: “who must review this file?”. Branch protection asks: “did at least one of those reviewers actually approve?”.

Without CODEOWNERS, branch protection’s “minimum N approvals” is a generic gate: anyone with merge rights can be one of the N. The gate is satisfied by two approvals that may have nothing to do with the files that changed.

Without branch protection, CODEOWNERS’ routing is a routing suggestion: a request to a named engineer with no enforcement on whether the engineer actually approved before the merge.

The coupling is what makes the two layers useful. Branch protection configures “require review from Code Owners”; CODEOWNERS configures “who reviews what”. Each is necessary and neither alone is sufficient.

flowchart LR
    A[CODEOWNERS routes owners] --> F[Enforcement stack]
    B[Branch protection requires owners] --> F
    C[Status checks verify code] --> F
    D[Signed commits verify identity] --> F
    E[Audit logs record events] --> F

The same layered pattern applies to:

  • Status checks. Branch protection configures “which checks are required”; the CI system produces them. Each is necessary.
  • Signed commits. Branch protection configures “require signed commits” or “require verified signatures”; the signer produces them. Each is necessary.
  • Audit logs. Branch protection configures “do not allow bypass by administrators” (or a tighter bypass list); the audit log records every bypass event. Each is necessary.

The relationship with code review policy

Branch protection enforces the minimum of code review; it does not enforce the quality of code review. A team policy that goes beyond minimums - “every PR has a security reviewer on auth-related changes”, “every PR is reviewed by someone who has deployed the system in the last six months” - is enforced through CODEOWNERS, not through branch protection. CODEOWNERS matches the paths to owners; the owner is, by being listed, a named reviewer who must approve. The team policy holds because the owners listed are the right reviewers for those files.

Branch protection is the shell of code review policy: the minimum requirements, the gate that catches a PR missing the minimums, and the spot where bypass happens. The contents of code review policy live in CODEOWNERS, in team practices, and in the lint, format, test, and security gates that the status checks expose.

Codifying the configuration

The branch-protection configuration belongs in source control. Forges vary in their native support:

  • GitHub: Repository rulesets can be set via the REST API and stored in a JSON file in a central configuration repository, applied via a script that runs on rule changes.
  • GitLab: Protected branches and approvals can be configured via the API and codified as a .yml in a configuration repository.
  • Bitbucket: Branch-permission configuration is exposed via the API.
  • Self-hosted forges (Gitea, Gerrit, CodeCommit): A declarative configuration is the typical pattern, often applied with a sync on every change to the configuration repository.

The codification turns branch protection from a UI-only artefact into a reviewed, auditable change. A change to the rule is a pull request that the team can review, comment on, and version. Without codification, a rule change is a click in the UI, visible only to the engineer who clicked, recoverable only by examining the audit log of repository-settings changes.

A subtle point: the codification repository itself is a production resource. It is the place where the policy lives, and that place must be protected at the same standard as the infrastructure repositories the policy applies to. The configuration repository typically has stricter branch protection than the repositories it configures, not looser.

The limits of branch protection as policy

Branch protection cannot enforce policy that is not in a form the forge can evaluate. Three categories of policy fall outside the rule set:

  1. Quality of review. A reviewer can approve without reading. Branch protection does not catch that; CI does not catch that; signed commits do not catch that. The discipline is in CODEOWNERS and in team practice, not in the forge.
  2. Tests passing over time. A passing check at merge time does not certify the same check still passes after later changes land. Branch protection is forward-looking; it evaluates the merge candidate, not its future. The discipline is in CI and in test maintenance.
  3. Architectural or operational decisions. A merge that crosses the team’s “no new dependencies without an architecture review” rule passes branch protection if the rule is not encoded as a required check. The discipline is in pre-merge architecture discussion, not in the forge.

Branch protection is the right control for the things it can evaluate: was the change reviewed, by an approved reviewer, in the current commit, with a current CI result. For everything else, it is the wrong layer. Codifying the limit is part of the policy.

Production discipline

Five rules for branch protection as policy:

  1. Every team policy statement is a rule. Prose in a wiki is not a control.
  2. The rule set is in source control. Changes to the rule are pull requests, reviews, audits, like any other production change.
  3. The configuration repository is higher-criticality than what it configures. Stricter branch protection than the repositories it configures, real-time alert on rule changes, named owner with on-call responsibility.
  4. Bypass is configured narrowly and is alerted. Two to four named individuals; every bypass event pages; every bypass event has an incident ticket.
  5. The policy is paired with a limit document. The statement of what branch protection can and cannot enforce is part of the policy itself, so the team does not expect the rule set to catch what it cannot.

Cross-course references

  • Linux for Production Sysadmins - Part XIV (AuditLogging) covers the OS-level audit posture that complements branch protection.
  • Ansible for Production Sysadmins - Part XXXVIII (QualGates) walks the same enforcement-stack pattern in Ansible.
  • Terraform for Production Sysadmins - Parts XXXII-XXXIII (RepoGuard / StateGuard) cover the same enforcement-stack pattern on Terraform code and state.

Quiz

Knowledge check · 4 questions

  1. Q1. A team policy says 'every change to the default branch is reviewed by two engineers'. The team has a branch protection rule requiring one approval. What is the gap and how should it close?

  2. Q2. Codifying the branch-protection configuration in a separate repository removes the need for branch protection on that configuration repository itself.

  3. Q3. List the three other layers in the governance stack that pair with branch protection to cover what a rule set alone cannot enforce.

  4. Q4. Diagnose why a team policy 'no production deploys on Friday' is not being enforced despite a stated branch-protection policy.

    The team policy says 'no production deploys on Friday'. Production deploys are gated by an Argo CD Application that watches the default branch. A PR was merged on a Thursday, the CI pipeline ran, the merge was approved by two reviewers, and the Argo CD sync occurred on Friday. The team asks: the branch protection rule was satisfied - why did the Friday deploy happen?

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