
May 02, 20256 min read
Doorman started as a way to stop copy-pasting firewall rules into the Vercel dashboard. Two versions later, it manages Cloudflare WAF too.
I kept running into the same problem when projects grew beyond a single Vercel deployment: some traffic goes through Cloudflare, some doesn’t, and the firewall rules that protected one weren’t automatically protecting the other. Keeping those two configs in sync by hand was the kind of thing that breaks at 2am. So I built Doorman to treat firewall rules the same way we treat everything else – as code, in version control, deployed through CI.
If you’re new here, the original post covers why I built it and how v1 worked. This post is about what’s new in 2.0.
Version 1.x handled Vercel Firewall well. With 2.0, Cloudflare WAF is a first-class provider. One config file, one CLI, two providers.
Every command that worked against Vercel now works against Cloudflare with a --provider cloudflare flag. Full CRUD for Cloudflare Rulesets and Rules, Lists API integration for bulk IP blocking, and provider-aware validation and health scoring throughout.
# Target Cloudflare with any command
vercel-doorman sync --provider cloudflare
vercel-doorman list --provider cloudflare
vercel-doorman status --provider cloudflare
There’s also automatic rule translation between Vercel and Cloudflare formats. The translation engine uses an expression builder for Cloudflare’s wirefilter syntax, maps field names between providers, and surfaces warnings when a conversion is lossy (regex rules on non-Enterprise Cloudflare plans, for example).
The default config filename is now .doorman.json – shorter, provider-agnostic, and matches the project name. Existing vercel-firewall.config.json files are still auto-detected and work unchanged, so there’s nothing to migrate if you’re already using v1.
{
"$schema": "https://doorman.griffen.codes/schema.json",
"provider": "cloudflare",
"providers": {
"cloudflare": {
"zoneId": "your_zone_id",
"accountId": "your_account_id"
}
},
"rules": [],
"ips": []
}
Underneath the CLI, a unified IFirewallProvider interface means the same command surface works identically across providers. Provider detection follows a clear priority: --provider flag, then the provider field in config, then provider-specific config keys, then environment variables (DOORMAN_PROVIDER, CLOUDFLARE_ZONE_ID, VERCEL_PROJECT_ID), and finally Vercel as the default.
Every error now includes a code, an actionable suggestion, and a documentation link. I got tired of cryptic API failures that didn’t tell you which credential was wrong or where to fix it.
[PROV_5000] Authentication failed for cloudflare
Suggestion: Verify your CLOUDFLARE_API_TOKEN is valid and not expired.
Create a new token at dash.cloudflare.com/profile/api-tokens
Documentation: https://doorman.griffen.codes/docs/cloudflare-setup
A few things that quietly make the CLI more resilient: API response caching with TTL-based invalidation, request deduplication for concurrent operations, exponential backoff with jitter on retries, batch processing for large rule sets, and graceful shutdown that preserves progress mid-run.
The test coverage jump was intentional. Multi-provider code has a lot of surface area and I wanted to be confident the abstraction layer held before shipping.
| Metric | v1.5 | v2.0 |
|---|---|---|
| Providers | 1 (Vercel) | 2 (Vercel + Cloudflare) |
| Test suites | 26 | 55 |
| Tests passing | 410 | 1,132 |
| New source files | – | 47 |
| Lines of new code | – | ~3,600 |
| TypeScript errors | 0 | 0 |
Nothing breaks. Provider defaults to Vercel when not specified, existing config files are auto-detected, and all the same commands work the same way.
npm install -g vercel-doorman@latest
To start managing Cloudflare from the same config:
export CLOUDFLARE_API_TOKEN="..."
export CLOUDFLARE_ZONE_ID="..."
vercel-doorman init --provider cloudflare --interactive
If you’re already using Doorman and run into anything weird with the Cloudflare integration, open an issue or find me on Discord. And if you’ve been waiting for the Cloudflare support to give it a try, now’s a good time.
Discussion
Comments are powered by Disqus. Sign in once, comment anywhere.
