GitGENERALTOOL-SPECIFIC

Source Control as Production Infrastructure

When merging triggers delivery, the repository stops being a record of what happened and becomes the control plane for what production is.

The question, the obvious approach, and why it breaks

Every lesson starts where the work starts: an operational problem, a first attempt that is entirely reasonable, and the way production disagrees with it.

The production question

If a merge can change production, what does that make the repository, and what does it owe you?

The problem

Version control is taught as collaboration and history. Once a pipeline deploys on merge, and once infrastructure, configuration and policy live in the same repository, a commit is a proposed production state — with all the access, review and availability implications that implies.

What teams do first

Git is where the code lives. It is a developer tool. Production concerns start at the pipeline, which reads from the repository.

How it breaks

Write access to a branch that auto-deploys is effectively production access, granted through a different system with different rules than the one that governs production (Production Access).

How it breaks in production
  • Write access to a branch that auto-deploys is effectively production access, granted through a different system with different rules than the one that governs production (Production Access).
  • The pipeline definition usually lives in the repository too, so someone who can change a workflow file can change what the pipeline does — including what it checks and where it deploys (CI Security).
  • A secret committed to a repository is in the history, in every clone, and in every fork. Deleting the file does not remove it; rewriting history does not remove it from copies already taken (When Secrets Fail).
  • History rewritten on a shared branch invalidates the mapping from commit to deployed artifact — the identity your whole delivery chain relies on (From Developer to Users).
  • If the repository host is unavailable, and your rollback path requires re-running a pipeline from a commit, your recovery depends on a third party being up during your incident.
CodeBuildTestArtifactReleaseDeployRunObserveOperateIncidentRecoverLearnImprove

What is actually happening

Underneath the tooling, which is the part that survives a change of tool.

  • Git stores an immutable object graph: commits address trees, trees address blobs, and every object is identified by a content hash. Nothing in a commit can change without changing its identity.
  • Branches are mutable pointers into that immutable graph. That distinction is the whole security model: objects are tamper-evident, but which commit a branch name refers to is a mutable piece of state that can be moved by anyone with write access, including backwards.
  • A force push moves a branch pointer to a commit that is not a descendant of the old one. The old commits still exist until garbage collection, but nothing references them, so anything that recorded "branch main at time T" no longer resolves to what it saw.
  • Because delivery reads from branch pointers, the branch pointer becomes production-relevant state. Protecting it is protecting production, and that is what branch protection rules exist for (Protected Branches).
  • The repository typically holds four kinds of production input, each with a different blast radius: application code, infrastructure definitions, deployment configuration, and pipeline definitions. Reviewing them at the same level is a mismatch, since the last two can change the meaning of the first two.
  • Commit signing binds a commit to a key, which addresses "who wrote this" rather than "who merged this". Both matter and they are different questions (The Audit Trail).

What is actually in the repository, by blast radius

A single review process usually covers all of these, which means the same attention is spent on a copy change and on the file that decides where the pipeline deploys. Sorting them by blast radius is the first step to routing them differently.

What it isWhat a wrong change doesBlast radiusWho should review it
Application codeWrong behaviour, caught by tests and canaryOne serviceService owners
Deployment configWrong replica count, wrong environment, wrong endpointOne service, immediatelyService owners, with schema validation (Validate at Startup, Fail Clearly)
Infrastructure definitionsDeleted or replaced resources on applyUp to a whole environment (Destructive Changes: What a Rename Really Does)Infrastructure owners, with a reviewed plan
Pipeline definitionsChanges what is checked and where it deploysEverything the pipeline can reach (CI Security)Owners of the delivery path
Policy as codeRemoves a guardrail everyone relies onEvery service the policy covers (Policy as Code)Platform and security owners
MigrationsData changed or destroyed on applyThe dataset, potentially unrecoverably (Destructive Migrations)Someone who has rehearsed it at scale

Commit to production state

GENERALThe shape holds for push-based pipelines and for pull-based reconcilers alike; in the pull-based case the reconciler watches the branch pointer instead of a webhook firing, but the pointer is still the production-relevant state (Reconciliation: The Loop Under Everything).

The path from a merge to a changed production system, with the two places where the repository is doing something other than storing text: the branch pointer that delivery watches, and the pipeline definition that comes from the same repository it is deploying.

The repository as control plane
must passmerge moves the pointertriggersbuildsdeployscan also change the pipeline itselfAuthorChange proposalRequired checksProtected branch pointerPipeline (defined in the same repo)Artifact by digestProduction
UserLLMAgentToolDataDecisionHumanGuardrail

Repository failures that become production failures

Each row starts as something that looks like a source-control mishap and ends as a production event, because delivery reads from the repository.

TriggerSymptomCauseResponse
Force push to the deploying branchPipeline deploys an unexpected commit; the running version no longer matches what anyone reviewedBranch pointer moved to a non-descendant commitBlock force pushes on protected branches; recover by pushing the correct commit forward, never by rewriting again (Protected Branches)
Credential committedValid credential present in history, clones and forksNo pre-merge secret scanningRotate and revoke immediately; removal from history is cleanup, not remediation (Rotation That Applications Survive)
Workflow file edited in a change proposalChecks pass that did not run, or an artifact is published from an unreviewed buildThe pipeline definition is an input to the pipeline that validates itTreat pipeline changes as privileged; restrict which triggers can use deployment credentials (CI Security)
Merge on a stale baseMain is broken although every check was greenChecks ran against a base that no longer matches the merge result (Required Checks)Require the branch to be current, or use a merge queue that tests the actual merge result
Deploying branch deletedDeployments stop; reconcilers may prune resourcesNo deletion protection on a branch that delivery depends onProtect against deletion; know whether your reconciler prunes on a missing source (Reconciliation: The Loop Under Everything)
Repository host outage during an incidentCannot roll back because rollback rebuilds from sourceRecovery path coupled to a third-party dependencyMake rollback a redeploy of an existing digest, not a rebuild (Build Once, Deploy Many)

How to do it properly

Most important first.

  • Treat write access to a deploying branch as production access, and review it on the same cycle as production permissions (Access Review).
  • Protect the branches that delivery reads from: no direct pushes, no force pushes, no deletion (Protected Branches).
  • Scan for secrets before they can be committed, and treat any committed credential as compromised and rotate it rather than deleting the file (Rotation That Applications Survive).
  • Route review of pipeline definitions and infrastructure code to people who understand their blast radius, not to whoever reviews application code (Destructive Changes: What a Rename Really Does).
  • Keep the commit SHA as the identity that flows through build, artifact and running process, so the repository and production can always be joined (Build Provenance).
  • Know your recovery path if the repository host is down: whether you can redeploy an existing artifact by digest without re-running a build is a question worth answering before you need it (Disaster Recovery as an Operation).

How much can this affect

Every production change has a blast radius. Stated as a scale so it is comparable between changes rather than adjectival — and paired with what actually contains it, because a wide scope with a real containment mechanism is a different situation from a wide scope with none.

Blast radius if this is wrongEveryone
One testEveryone
What contains it

Branch protection, required checks and progressive deployment. With auto-deploy on merge and no protection, a single push reaches everyone at pipeline speed.

What can go wrong

Failure modes, including of the mitigation
  • Broad write access granted for convenience — bot accounts, shared credentials, an "everyone" team — that quietly becomes broad production access.
  • A workflow file change that alters what the pipeline deploys, reviewed as if it were an application change.
  • A secret rotated but not revoked, so the old value still works and is still in the history.
  • A repository that deploys on merge with no protection at all, where a mistaken push is a production incident.
  • Rollback that requires rebuilding from source, so a repository or registry outage becomes an availability incident for your recovery path (Rollback: Only Useful If It Is Actually Safe).
Misreads this invites
  • "Git history is immutable." Objects are immutable; branch pointers are not. A force push does not change history, it changes which history is referenced (Protected Branches).
  • "We removed the secret in the next commit." The old commit is still reachable, still in clones, and still in forks. Rotate it (When Secrets Fail).
  • "GitOps means storing everything in Git." Storing declared state in a repository is the easy half; the interesting half is a reconciler continuously converging reality onto it (Reconciliation: The Loop Under Everything).
  • "The repository is a backup." It holds source, not state. Your database is not in it, and neither is the artifact you would roll back to (Backup Operations).

Operating it

Evidence is the signal, not the intention. Rollback is sometimes 'you cannot, and that is the point'.

How you know it worked
  • You can list who can write to the deploying branch, and the list is not surprising.
  • Branch protection is enforced on the branches delivery reads from, and its settings are themselves reviewed.
  • Every deployed artifact names a commit that still resolves in the repository.
  • A secret-scanning check runs before merge and has actually blocked something.
  • You have redeployed a previous artifact without rebuilding it, so you know that path works (Build Once, Deploy Many).
How you get back
  • Reverting a change is a new commit, which is the right mechanism on a shared branch: history stays append-only and the revert is itself reviewable and deployable.
  • Rolling production back does not require reverting the commit. Redeploying the previous artifact digest is faster and decouples the code decision from the production decision (Tags Versus Digests).
  • History rewritten on a shared branch cannot be cleanly rolled back — every clone that fetched the old history has to be reconciled by hand. This is why force-push protection exists.
  • A committed secret cannot be rolled back at all. The only recovery is rotation, on the assumption it has been read (Rotation That Applications Survive).
What to automate, and what stays human
  • Automate: secret scanning, protection settings applied as code rather than clicked in a UI, ownership-based review routing, and signature verification where the threat model needs it.
  • Automate the join between commit and running artifact, so nobody maintains a mapping manually.
  • Keep human: granting write access to deploying branches, and approving changes to the pipeline definition itself (How to Automate Something).
What this costs
  • Treating the repository as production infrastructure adds friction to a tool people chose partly for its lack of friction.
  • Protection rules are enforced by the hosting platform, so you are trusting that platform's policy engine — a dependency worth being explicit about.
  • Storing infrastructure, config and application code together makes changes atomic and makes blast radius uneven within one review process; splitting repositories fixes the second and breaks the first.

Where this applies

This domain is unusually tool- and organisation-dependent. These labels say what each claim is specific to, and what a different platform, provider or organisation does instead.

  • GENERALThe object model and the mutable-pointer distinction are properties of Git itself, identical on any host. What varies is which of these protections your host offers and how they are named.
  • TOOL-SPECIFICBranch protection, required status checks, CODEOWNERS-style routing and merge queues are hosting-platform features, not Git features. GitHub, GitLab and Bitbucket implement overlapping but non-identical sets, and a self-hosted plain Git remote has none of them — there you need server-side hooks to get equivalent enforcement.

Where the depth lives

This domain teaches delivery and operation, and hands the mechanism off to the domain that owns it.