Everything looked fine.
CI was green. ✅
The deployment succeeded. ✅
The platform URL worked. ✅
Then I opened the domain that actual users were supposed to visit.
403.
That was the moment I realized something obvious in hindsight:
A successful deployment does not necessarily mean your production site works.
The weird part
The application itself wasn't broken.
The build completed successfully.
The deployment platform said everything was healthy.
Even the platform-provided URL worked.
But my real production domain didn't.
In my case, the real production domain was returning a Cloudflare 403, while the platform-provided URL still worked.
That immediately pointed the investigation away from the build itself and toward the production edge layer: custom-domain routing, Cloudflare, or WAF behavior.
So from the CI pipeline's point of view:
build → deploy → success
From the user's point of view:
open website → 403
Two completely different realities.
CI was telling the truth
The strange thing is that CI wasn't wrong.
It did exactly what I asked it to do.
It verified that the code built successfully and that the deployment command completed.
What it didn't verify was everything that existed after deployment:
DNS
↓
custom domain
↓
TLS
↓
redirects
↓
CDN / proxy
↓
WAF
↓
JavaScript + CSS
↓
browser runtime
↓
what the user actually sees
A green CI checkmark only tells me that the pipeline finished.
It doesn't necessarily tell me that the production experience works.
That gap bothered me.
So I started checking the real production path
The obvious first solution was adding a request after deployment:
curl -f https://example.com
That's already better than doing nothing.
But then I started thinking about all the ways production can still be broken.
A page can return HTTP 200 but contain the wrong deployment.
The HTML can load while a critical JavaScript bundle returns 404.
A custom domain can reach a different route from the platform URL.
A CDN or WAF can block the request.
The server response can look healthy while the browser crashes on a JavaScript error.
A redirect can quietly send users somewhere completely different.
So checking one status code wasn't really what I wanted.
I wanted something that could answer:
Does the production experience users actually hit work, and if not, where did it break?
That became ProdDoctor 🩺
I turned that debugging workflow into an open-source GitHub Action called ProdDoctor.
Instead of trusting the deployment result, ProdDoctor checks the real production URL after deployment.
It currently validates things like:
- DNS resolution
- HTTP status and redirects
- expected page content
- TLS certificates
- same-origin JavaScript and CSS assets
- common Cloudflare Challenge / WAF behavior
- optional real Chromium rendering
- uncaught JavaScript errors
- failed critical browser requests
- screenshots
- Playwright traces
- HTML and JSON reports
The part I care about most isn't adding another red or green checkmark.
It's narrowing the failure down to the layer that probably broke.
Instead of getting:
❌ Production check failed
I want something closer to:
DNS PASS
TLS PASS
HTTP FAIL (403)
Cloudflare Challenge / WAF suspected
Now I know where to start looking.
The current stable release, v2.1.0, also adds a likely-cause summary to make failed checks easier to diagnose.
The setup ended up being tiny
For a basic production check:
- uses: lucaswenbo/ProdDoctor@v2.1.0
with:
url: https://example.com
expect: My Website
That's a GitHub Actions step.
Put it after your deployment step and point it at the real public production URL, not the platform preview URL.
For example:
name: Deploy and verify
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
# Your existing build and deployment steps go here.
- name: Verify real production domain
uses: lucaswenbo/ProdDoctor@v2.1.0
with:
url: https://example.com
expect: My Website
The default mode stays lightweight.
No Cloudflare API token is required.
If I need deeper evidence, I can enable browser validation:
- uses: lucaswenbo/ProdDoctor@v2.1.0
with:
url: https://example.com
browser: true
ProdDoctor can then launch Chromium and preserve evidence such as a full-page screenshot, browser failures, a Playwright trace, and reports.
Why HTTP 200 isn't enough
This was another thing that became obvious while building it.
Imagine this:
HTML ✅ 200
app.js ✅ 200
TLS ✅
Browser render ❌ white screen
Or:
HTTP ✅ 200
Expected content ❌ missing
Or even:
Platform URL ✅
Custom domain ❌ 403
All three can happen after a deployment system happily reports success.
That's why ProdDoctor separates lightweight HTTP-level checks from optional browser-level validation.
Most sites don't need Chromium on every run.
But when the problem only appears after JavaScript executes, a simple HTTP request can't see the whole picture.
I'm not trying to build another giant monitoring platform
I want ProdDoctor to stay focused on one question:
Did the thing I just deploy actually work for the user?
There are already great monitoring and observability platforms.
ProdDoctor is aimed at a slightly different moment:
code
↓
CI
↓
deploy
↓
real production URL
↓
verify what users actually get
That last step was the one missing from my own workflow.
One important limitation
Cloudflare / WAF detection is heuristic.
ProdDoctor doesn't access your Cloudflare account or read private WAF logs.
It looks at what a public client can observe, including status codes and common Challenge markers.
So if it says a Cloudflare Challenge or WAF is likely involved, that's a debugging direction, not magical access to Cloudflare's internal decision logs.
I think that distinction matters.
The lesson I took from this
I used to think the pipeline ended here:
code → CI → deploy ✅
Now I think it should end here:
code
↓
CI
↓
deploy
↓
production
↓
user-visible behavior ✅
The deployment platform knows whether it successfully deployed something.
The production URL tells you whether users can actually reach and use it.
Those are not always the same thing.
I'm still building it
ProdDoctor is open source, and I'm deliberately keeping it focused on production validation.
👉 Check out ProdDoctor on GitHub
Current stable release: v2.1.0
I'm especially interested in failures that slip through normal CI:
- custom-domain failures
- CDN / WAF surprises
- broken assets
- browser-only crashes
- stale or wrong deployments
- weird redirect behavior
What's the strangest "CI was green, production was broken" incident you've run into?
If there's a failure mode ProdDoctor doesn't catch yet, tell me about it.
Real production failures are exactly what I want to turn into the next checks.
Top comments (0)