By Christopher Diggs, Founder & Principal Engineer
Push-to-deploy without the accidents
Connecting git to your host means a stray push reaches customers. Here's the gate we put in front of the site that takes payments.
Push-to-deploy is the right default for most projects. Commit, push, and the change is live, with no CLI ceremony, and every branch gets a preview URL you can hand to a client.
It's the wrong default for exactly one kind of project: the one that moves money. When a client billing portal deploys on every push to the main branch, there is nothing between a distracted afternoon and a production incident with real invoices attached.
The gate is a second branch
Rather than disconnecting git entirely and losing preview builds, point the production environment at a dedicated branch. Day-to-day work pushes to main and produces a preview; shipping is an explicit, differently-shaped command.
git push origin main # preview URL only - safe
git push origin main:production # deploys production - deliberateThe protection isn't cryptographic. Nothing stops you pushing to the production branch. What it buys is that the dangerous action no longer shares a command with the safe one, so it can't happen by muscle memory.
Verify the gate, don't assume it
We nearly learned this the wrong way. During the same audit we pushed some catch-up commits to another project, not realizing it was already connected to its host, and that push deployed production without anyone intending it. It built cleanly and no harm came of it, but the lesson stands: check whether a repository is already wired to deploy before you push to it, not after.
After changing any deploy configuration, prove it with a real push and watch which environment the build lands in. A settings page that claims a gate exists is not the same as a gate that has been observed working.
Where teams get this wrong
- Assuming branch protection is available. On several hosting plans it simply isn't offered for private repositories, and the feature fails silently until you try to configure it.
- Treating a green deploy as evidence that safeguards work. It's evidence the deploy path works, nothing more.
- Leaving the manual override in place without documenting it. A direct CLI deploy still bypasses every branch gate you built.
Why not just use branch protection?
Because it may not be available to you. Repository rulesets and classic branch protection are plan-gated on the major hosts: on a free plan with private repositories, the API refuses outright and the dashboard doesn't offer it.
That leaves three options. Pay for the plan tier that unlocks it, which is reasonable if you have several repositories worth protecting. Make the repository public, which is not an option for client billing code. Or move the gate to the deploy layer, which is what the production-branch approach does.
Be honest with yourself about what you've bought. A branch gate is a speed bump, not a lock. Anyone can push to the production branch, and a direct CLI deploy bypasses git entirely. What it prevents is the accident, not the decision, and accidents are what actually cause incidents.
The setting is harder to find than it should be
On at least one major host the production branch setting has moved out of the git configuration page and into environment settings, so following older documentation leads you to a page where the option no longer exists.
It also may not be writable through the API. Attempting to set it programmatically can be rejected as an unknown property while the dashboard changes it happily. Budget a few minutes of clicking, and verify the result with whatever command lists your environments rather than trusting that the save took.
The goal isn't to make production unreachable. It's to make reaching production a decision rather than a reflex.
We build this kind of thing for a living.
Start a conversation