You moved 12 client domains to Cloudflare for speed and security. Now client calls: "Why is my email bouncing?" You open Cloudflare, search zoneDomain, find acme.in on Free plan under gautam.personal@gmail.com, realize MX was pointing to old host before migration. You fix it, but the hour-long hunt across 3 Cloudflare accounts cost you half a day.
Freelancers love Cloudflare. They hate console hunt — finding which zoneDomain lives under which accountEmail, on which plan, linked to which domain asset and which vault login, before you can change one DNS record safely.
The Cloudflare Sprawl Nobody Talks About
Account chaos. One accountEmail grows to three — yours, client's, agency's — and assetsCloudflare zoneDomain src/lib/db/schema.ts:166 scatters. You can't tell beta.co Free vs Pro without 3 logins. Zone not linked. Sheet acme.in → GoDaddy → expiry 2026-11-02 and Cloudflare acme.in → Free lack domainAssetId field, so no expiry 7d + Free + autoRenew false single view. Credentials in limbo. admin@client.com in LastPass not linked to assetsCloudflare — hand-off is two tools, client reassembles. Plan blind. Without plan src/lib/db/schema.ts:168 joined to domainName src/app/api/assets/[category]/route.ts:162, you miss Pro WAF need until attack.
Spreadsheet CF Zones with Zone | Account | Plan is not security. It's storage without linkage, without encryption for the login beside it, and without alerts when the underlying assetsDomains expiryDate hits 7.
The Linked Cloudflare Graph in RunoSO
RunoSO separates six asset types for a reason. Cloudflare isn't a column in domains — it's its own table with FKs:
assetsCloudflare {
zoneDomain varchar(255) notNull src/lib/db/schema.ts:166
accountEmail varchar(255) notNull src/lib/db/schema.ts:167
plan varchar(100) src/lib/db/schema.ts:168 // Free | Pro | Business
domainAssetId uuid fk → assetsDomains.id src/lib/db/schema.ts:169 // links zone to domain asset
clientId uuid fk → clients.id src/lib/db/schema.ts:170 // whose zone
notes text src/lib/db/schema.ts:171
}
// API joins: .leftJoin(assetsDomains) + .leftJoin(clients)
// src/app/api/assets/[category]/route.ts:154-172
// POST validates: cloudflarePostSchema { zoneDomain, accountEmail: email, plan, domainAssetId uuid }
// src/app/api/assets/[category]/route.ts:35-42What linked gives you: domain↔zone in one domainAssetId join — see registrar+expiry+autoRenew+dnsProvider with zone+accountEmail+plan no hop; encrypted vault AES-256-GCM gcm:iv:tag:enc src/lib/vault/crypto.ts:22 + clientId src/lib/db/schema.ts:125 → one-click copy; full client view domains+cloudflare+hosting+github via clientId; and DOMAIN_MILESTONES [14,7,3,1,0] src/app/api/cron/daily/route.ts:16 → notifications domain_expiring + digest before zone breaks.
Setup in 20 Minutes
1. Import zones. POST cloudflare validates zoneDomain + accountEmail email. Link domainAssetId to assetsDomains. 2. Tie to vault. Create vaultItems api-key|password usernameEncrypted=accountEmail encrypted gcm:iv:tag:enc src/lib/vault/crypto.ts:28 — zone holds hint, vault holds secret. 3. Verify client. clients/[id] shows zone count — Beta LLC 0 with proxied DNS = gap before SSL incident.
Cloudflare Hunt vs Linked OS: The Real Cost
| Task | 1Password / LastPass + Sheets | RunoSO Assets + Vault |
|---|---|---|
| Find zone's account | Search vault cloudflare → 6 entries, guess email | assetsCloudflare accountEmail typed src/lib/db/schema.ts:167 + domainAssetId join shows zoneDomain → domainName src/app/api/assets/[category]/route.ts:162 in one row |
| Find domain expiry for zone | Open separate Domains sheet, VLOOKUP | One client view → domain expiryDate + autoRenew src/lib/db/schema.ts:153 beside zone plan — JOIN already done |
| Get login securely | Copy plaintext password from shared sheet risk | vaultItems AES-256-GCM src/lib/vault/crypto.ts:22 + clientId link → 1-click copy, never stored with asset notes |
| Know when to act | Calendar reminder you snooze | Cron daily 03:30 UTC vercel.json:4 → daysUntil(expiryDate) src/app/api/cron/daily/route.ts:19 → 14/7/3/1/0 notifications + digest before domain parking breaks zone |
| Handover to client | Export vault CSV + sheet → client rebuilds links | portalTokens src/lib/db/schema-public.ts:169 → one link shows domains + zones + hosting + repos for that clientId — no login, revocable |
| Scale to 30 domains | Sheets VLOOKUP degrades, vault search polluted | Six tables ALLOWED_CATEGORIES ["domains","cloudflare","github","hosting","social","subscriptions"] src/app/api/assets/[category]/route.ts:91 per tenant_${id} src/lib/db/tenant.ts:67 paginated buildPaginatedQuery |
1Password encrypts login. Sheets lists zone. Only RunoSO encrypts + links + alerts — so when DNS misroutes, you fix in 2 minutes, not 60.
Mistakes That Keep the Hunt Alive
Wrong accountEmail. admin vs admin@client.com fails z.string().email() src/app/api/assets/[category]/route.ts:37. No domainAssetId. Null leaves domainName null in join src/app/api/assets/[category]/route.ts:162 — lose expiry+zone pane. No plan. Track Free/Pro for WAF and upsell filter.
Monthly 15-Minute Review
Sort Domains by expiryDate — ≤14d already notified domain_expiring. Filter Cloudflare plan Free for upgrade talk. Audit vault clientId tokens if contractor left.
Cloudflare is powerful. Hunting through three accounts and a sheet to use it is not. Centralize zones with domains, link encrypted logins, and let milestones alert you.
→ Secure Your Domains in RunoSO — Free
By Gautam Parmar — building the OS freelancers deserve, one linked asset at a time.
Keep Reading
- Domains first: Domain renewal checklist never lose domain.
- Vault beside assets: How to organize hosting server credentials.
- Full map: Digital asset management system freelancers.
FAQ
Why not just use Cloudflare dashboard for all zones? Dashboard shows zones per account, not per client/project. RunoSO adds clientId + domainAssetId joins src/app/api/assets/[category]/route.ts:169 so Acme Corp → domains + zones + vault credential appear together — business context GitHub/Cloudflare dashboards never provide.
How are Cloudflare logins stored safely? As vaultItems rows with usernameEncrypted/passwordEncrypted/metadataEncrypted src/lib/db/schema.ts:129 encrypted via encryptGCM gcm:iv:tag:enc src/lib/vault/crypto.ts:22 derived via deriveKey SHA-256 src/lib/vault/crypto.ts:18. Database dumps return ciphertext; asset table holds only accountEmail hint.
How do I hand over a client's Cloudflare without exposing other clients? Create a portalTokens token src/lib/db/schema-public.ts:169 scoped to tenantId + clientId unique. Share the portal/[token] link — client sees only their assetsCloudflare + assetsDomains bundles, revocable via revokedAt without touching your vault master key.



