A green build proves the code compiled; it does not prove the client approved what visitors will see. Those are two different claims, and only one of them is a machine's job.
Continuous integration earns its keep on client work. Every push builds, type checks run, the preview URL appears in the pull request, and nobody has to remember a command. That part should be automatic and should stay automatic. The step people collapse into it, publishing that same working tree to the domain a client pays for, is a different decision with different stakeholders.
On client projects I keep those two steps deliberately separate, and the reason is not caution about code.
Most deployment advice treats those two steps as one pipeline with one setting. It is written for product teams shipping their own software to their own users, where the person merging is also the person who owns the copy, the brand and the consequences. A marketing site built for a client breaks that assumption. The person who can approve the sentence on the homepage is not in the repository.
What is the difference between continuous delivery and continuous deployment?

Continuous delivery keeps every build releasable and lets a human decide when it ships; continuous deployment removes that decision and publishes every passing change automatically. The difference is one human, not one pipeline.
That distinction gets blurred constantly, usually by tooling that calls both things CD. It matters here because the two words describe opposite defaults for the moment of publication. Under continuous delivery, a merged, built, verified artifact sits ready and someone promotes it. Under continuous deployment, merging is publishing, and the merge button carries whatever the working tree happened to contain.
Neither is more automated than the other in any way a client would notice. Both build automatically. Both test automatically. Both produce a preview. The DORA change approval research is careful about this too: its argument is with heavyweight external gatekeeping, not with a build being held until someone with authority says go. If you want the fuller vocabulary, the continuous integration and delivery glossary entry covers the terms without the vendor gloss.
Automatic production deploys are often an engineering convenience disguised as a product decision

Turning off the gate feels like rigor. It usually is not. It is the removal of an interruption that was inconvenient for exactly one person: the engineer.
Ask what the automatic setting actually optimizes. It saves a click and a context switch. It removes the awkward message asking a client to look at a preview URL again. It makes the deploy history tidy. Those are real benefits and they all accrue to the developer. Nobody chose them on behalf of the business whose domain is being published to.
Now ask what it costs. It transfers the publication decision to whoever merges next, at whatever moment they merge, with whatever the branch contains. On a product team that is fine, because the merger owns the outcome. On client work the merger owns the code and someone else owns the brand and the legal text. Automatic production quietly reassigns a decision from the person accountable for it to the person with commit access.
Actually — "quietly" is too generous. It is usually not a decision at all. It is the platform default, adopted because it was already on, and then defended afterward as a philosophy. Defaults are not strategies.
What a passing build does not check

A build verifies that code is valid. It has no opinion about whether the content is finished, approved, or pointed at the right environment. That gap is where client-site incidents live, and every item below passes CI.
| What the build confirms | What it cannot confirm |
|---|---|
| The bundle compiles and types resolve | The homepage headline is the version the client signed off on |
| Every route returns 200 | The pricing page is not still showing placeholder figures |
| No test regressions | The legal page is the reviewed draft, not the working one |
| Images are optimized and referenced | The new hero asset is the licensed one, not the comp |
| Metadata tags exist | A temporary noindex from staging is not still in the response |
| All locales generate | The German page is not silently rendering source-language copy |
The fifth row is the one that ships. A noindex header added during a rebuild, or a robots rule set while a section was unfinished, is invisible to everyone reviewing the page in a browser. It is also durable, because nothing about the site looks broken afterward.
These are not exotic failures. Ahrefs crawled 1,002,165 domains for its 2023 technical SEO study and found a missing or empty meta description on 72.9% of them, with incomplete Open Graph tags on 56%. Every one of those sites built fine. Metadata defects sit in exactly the blind spot a green pipeline creates: valid code, wrong output, no error anywhere.
And the visual class of failure is worse, because it is not detectable from the HTML at all. A layout that holds at 1440px and collapses at 390px is a correct build. So is a section that looks deliberate on your monitor and unreadable on the client's phone, in the browser they actually use.
Here is how that went on Elpida Solutions.
Elpida Solutions stays on a manual Netlify production deploy even though the build itself is automated. That gate has caught work that was technically valid but not ready for the public domain: draft decisions, responsive details and content still waiting for approval. The rule is simple: preview can be automatic; production remains an explicit act.
The wider build, covering five locales, a hardened contact flow and the operating rules that go with them, is written up in the Elpida Solutions multilingual website case study.
What should a client website deployment checklist include?
A client deploy checklist has nine steps, and the load-bearing ones are a preview URL, a responsive pass, written content approval, an explicit publish and a recorded rollback target. Each is something a build cannot do for you.
- Work on a branch or an isolated worktree. Never on whatever is checked out. A separate worktree costs nothing and removes the entire category of accidentally-committed local experiments.
- Build it the way the host will. Run the production build locally before anything goes anywhere, so you find the environment-variable mistake on your machine rather than in a deploy log.
- Send one preview URL. Not a screenshot. A screenshot is an argument about what the page looks like; a preview URL is the page.
- Walk the responsive matrix yourself. Real breakpoints, real device, the longest string in every language you support. Navigation and form validation break first because they are the last things anyone opens.
- Get content approval in writing. Email, message, anything with a timestamp. This is the step the whole gate exists to protect, and it is the step most likely to be skipped because the page "obviously" looks right.
- Commit clean. One coherent change, no leftover debug flags, and no temporary robots rules you meant to come back to.
- Publish as a deliberate act. A button, a command, an approval — something a person does on purpose, at a time they chose.
- Smoke test the live domain. Not the preview. Load the real URL, submit the real form, view source and confirm the indexability headers say what you think they say.
- Write down the rollback target. The deploy ID currently serving, before you replace it. Thirty seconds now, and it is the difference between a calm recovery and reading a deploy list under pressure.
Steps 5 and 9 are the two that get dropped, and they are the two that turn a bad deploy into a bad afternoon.
A manual deploy that is not locked is not a gate
This one bites people who think they already have a gate. Netlify's own deployment reference is blunt about it: with Git CD connected, "a manual --prod deploy is replaced by the next push to the production branch unless you lock the deploy in the UI", per the Netlify deployment patterns reference. Your careful manual publish survives until a teammate pushes a typo fix.
Where the approval gate actually lives on each host
Every major host ships this. It is almost never the default, and it is never called the same thing twice.
| Host | Mechanism | Where to set it |
|---|---|---|
| Netlify | Lock the published deploy; builds continue, publishing stops | "Lock to stop auto publishing" in the Deploys list |
| Vercel | Staged production deploys you promote by hand | Disable "Auto-assign Custom Production Domains" under Settings, Environments, Production, Branch Tracking |
| GitHub Actions | Environment protection rules with required reviewers | Environment settings: up to 6 reviewers, one approval needed, optional wait timer |
The Netlify mechanism is the smallest change to an existing project. Locked deploys pin the site to the latest published deploy while Netlify keeps building every push, so previews stay live and nothing reaches the domain until you publish. The same screen gives you the rollback: "Publish Deploy" on any previous successful deploy, no rebuild, instantaneous. One caveat that matters — with auto publishing back on, the next Git-triggered production deploy overwrites whatever you rolled back to.
Vercel models it as state rather than a lock. Turn off auto-assignment of production domains and a push to main produces a deployment marked Staged rather than Current; promoting it assigns the domains without a rebuild. From a script that is vercel --prod --skip-domain to stage and vercel promote to publish, which means the gate is scriptable rather than a dashboard ritual. The platform trade-offs between the two are covered in the Vercel and Netlify comparison, and the same reasoning extended across app hosts in Vercel, Railway and Fly for SaaS hosting.
GitHub Actions is the right answer when the approver should not be the author. Environment protection rules hold the deploy job until a required reviewer approves, allow up to six reviewers with only one approval needed, and include a "Prevent self-review" toggle for when segregation of duties is a contractual requirement rather than a preference.
One recent change is worth folding in if your production step is a local command. Netlify extended skew protection to CLI-triggered deploys in November 2025, from CLI 23.11.0 onward; before that it did not apply to CLI workflows at all. If you deploy production by hand from a terminal, that is a version-mismatch class of bug you can now close by upgrading.
When is automatic production deployment the right call?
Automatic production is right when whoever merges also owns the content and the consequences, and when rollback is fast enough that a mistake costs minutes. That describes most product teams and almost no client marketing sites.
The evidence against gates is real, and it deserves to be stated properly rather than dismissed. DORA found no support for the idea that a formal external review process lowers change fail rates, reporting in its 2019 State of DevOps Report that heavyweight external approval slows delivery while producing larger and riskier batches. Its recommendation is peer review captured in the development platform, backed by testing and monitoring.
Read what that finding is about. It is about an external body, whether a change advisory board, a change manager or anyone with no part in the work, standing between an engineer and production. The gate on a client site is not that. It is the content owner approving content, which is the one thing DORA's own guidance still leaves to a business decision. A client signing off on their own homepage is not a CAB reviewing a diff they cannot read.
The batch-size warning does apply, though, and it is the honest cost. A gate that turns into a weekly release train recreates the problem DORA actually measured: bigger changes, further apart. Keep the gate cheap. Preview on every push, approval on a page not a sprint, publish the same day approval lands.
So: automatic production for a product you own, a documented gate for a domain you do not.
Lock one deploy this week
Open the host dashboard for one client site and turn the gate on. On Netlify that is one click in the Deploys list. On Vercel it is one toggle under Environments. Then write the deploy step into the project's README, because a gate that lives only in your memory is not a process and will not survive the week you are on holiday.
Then check the thing most of us have never checked: if your current production deploy turned out to be wrong right now, how long would it take you to name the deploy ID you would roll back to?
