Deploy on Merge or Don't Merge
The Rule
If it can’t deploy to production on merge, it shouldn’t merge.
This isn’t radical. It’s how CI/CD is supposed to work. Yet most teams treat main like a staging ground—a place where code goes to wait for “the next release” or “when we’re ready.”
That’s not version control. That’s a glorified to-do list.
The Problem with Manual Deploys
Manual deploys introduce friction. And friction introduces risk:
- Time decay: Code sits in
mainfor hours, days, weeks. By the time it deploys, the context is gone. The author has moved on. The reviewer forgot what they approved. - Batching risk: Instead of deploying one change at a time, teams batch 10, 20, 50 commits into a single release. When something breaks, good fucking luck isolating the cause.
- Deploy anxiety: If deployments are rare and manual, they become Events. Teams schedule them. They warn each other. They hold their breath. That’s a sign the process is broken.
What Deploy-on-Merge Enforces
When main auto-deploys, the rules change:
- PRs must be production-ready. No “TODO: add tests before we ship.” No “This will break staging but it’s fine.” If it’s not done, it doesn’t merge.
- PRs stay small. You’re not batching 3000 lines of refactoring into one deploy. You’re breaking it into 10 incremental PRs, each safe to ship individually.
- CI becomes the gatekeeper. Tests must pass. Builds must succeed. Linters must be happy. If CI is green and the PR is approved, it ships. No exceptions.
- Rollbacks become trivial. One commit per deploy means one revert fixes the problem. No archaeology needed.
The Exceptions That Prove the Rule
“But what about breaking changes?”
Feature flags. If a change isn’t ready for users, hide it behind a flag. Deploy the code. Test in production. Flip the switch when ready.
“But what about database migrations?”
Deploy them in phases. Phase 1: add the new column. Phase 2: backfill data. Phase 3: migrate code to use the new column. Phase 4: drop the old column. Each phase is a separate deploy. Each is safe to revert.
“But what about coordinated releases?”
If your architecture requires deploying 5 services simultaneously, your architecture is the problem. Fix the dependencies. Make services independent. Then deploy them independently.
The Cultural Shift
Deploy-on-merge isn’t just a pipeline config. It’s a mindset:
- Code in
mainis live. Treat it that way. - PRs are deployment units. Scope them accordingly.
- CI is not a suggestion. If it’s red, you stop.
- Deploys are boring. That’s the goal.
The Tooling
This isn’t hypothetical. It’s how modern infrastructure works:
- Vercel deploys every push to
mainautomatically. - Render does the same.
- GitHub Actions + AWS Lambda takes 10 lines of YAML.
- Docker + Kubernetes can auto-deploy from tags.
The tools are there. The question is whether the team is disciplined enough to use them.
The Payoff
Once deploy-on-merge is the default:
- Deploys happen 10x more often. Each one is 10x less risky.
- Debugging is trivial. “What changed?” → One commit.
- Context stays fresh. The author who wrote the code is still thinking about it when it ships.
- Velocity increases. No waiting for “the next release window.”
The Hard Part
The hard part isn’t the automation. It’s convincing the team that main isn’t a staging area—it’s the source of truth for production.
If that sounds scary, the real question is: why isn’t main trustworthy?
Fix that first. Then auto-deploy is easy.
Deploy-on-merge is a forcing function. It exposes weak tests, unclear requirements, and architectural coupling. That’s the point. Fix the process, then automate it.