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.
If a merge can change production, what does that make the repository, and what does it owe you?
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.
Git is where the code lives. It is a developer tool. Production concerns start at the pipeline, which reads from the repository.
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).
- 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.
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 is | What a wrong change does | Blast radius | Who should review it |
|---|---|---|---|
| Application code | Wrong behaviour, caught by tests and canary | One service | Service owners |
| Deployment config | Wrong replica count, wrong environment, wrong endpoint | One service, immediately | Service owners, with schema validation (Validate at Startup, Fail Clearly) |
| Infrastructure definitions | Deleted or replaced resources on apply | Up to a whole environment (Destructive Changes: What a Rename Really Does) | Infrastructure owners, with a reviewed plan |
| Pipeline definitions | Changes what is checked and where it deploys | Everything the pipeline can reach (CI Security) | Owners of the delivery path |
| Policy as code | Removes a guardrail everyone relies on | Every service the policy covers (Policy as Code) | Platform and security owners |
| Migrations | Data changed or destroyed on apply | The dataset, potentially unrecoverably (Destructive Migrations) | Someone who has rehearsed it at scale |
Commit to production state
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.
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.
| Trigger | Symptom | Cause | Response |
|---|---|---|---|
| Force push to the deploying branch | Pipeline deploys an unexpected commit; the running version no longer matches what anyone reviewed | Branch pointer moved to a non-descendant commit | Block force pushes on protected branches; recover by pushing the correct commit forward, never by rewriting again (Protected Branches) |
| Credential committed | Valid credential present in history, clones and forks | No pre-merge secret scanning | Rotate and revoke immediately; removal from history is cleanup, not remediation (Rotation That Applications Survive) |
| Workflow file edited in a change proposal | Checks pass that did not run, or an artifact is published from an unreviewed build | The pipeline definition is an input to the pipeline that validates it | Treat pipeline changes as privileged; restrict which triggers can use deployment credentials (CI Security) |
| Merge on a stale base | Main is broken although every check was green | Checks 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 deleted | Deployments stop; reconcilers may prune resources | No deletion protection on a branch that delivery depends on | Protect against deletion; know whether your reconciler prunes on a missing source (Reconciliation: The Loop Under Everything) |
| Repository host outage during an incident | Cannot roll back because rollback rebuilds from source | Recovery path coupled to a third-party dependency | Make 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.
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
- 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).
- "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'.
- 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).
- 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).
- 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).
- 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.