Cloudflare Infrastructure Naming & Orchestration Proposal
Documentation for Cloudflare Infrastructure Naming & Orchestration Proposal
Date: 2026-03-30
Project: Neutrino Platform (nno)
Type: Multi-Tenant Platform Orchestration
Status: Revised Proposal
Overview
Neutrino is a multi-tenant platform orchestration layer built on top of Cloudflare infrastructure. It enables clients to provision, manage, and track isolated Cloudflare resource groups organized in a hierarchical structure:
Neutrino (meta-platform)
└── Platform ← Client's top-level environment
└── Tenant ← Business unit, team, or customer
└── Sub-Tenant (optional) ← Nested isolation within a tenant
└── Service ← Cloudflare resource (Worker, Pages, D1, R2, KV, Queue)
└── Environment ← dev / stg / prodNeutrino itself is deployed using the same convention — it has its own platform ID and follows the same naming rules as any client platform.
Cloudflare Naming Constraints (Verified)
Before defining any naming scheme, the hard limits imposed by Cloudflare must be respected:
| Resource Type | Max Length | Allowed Characters | Notes |
|---|---|---|---|
| Workers | 63 | [a-z0-9-] | No uppercase, no underscore |
| Pages Projects | 63 | [a-z0-9-] | No uppercase, no underscore |
| D1 Databases | 64 | [a-zA-Z0-9_-] | Slightly more permissive |
| R2 Buckets | 63 | [a-z0-9-] | S3-compatible, lowercase only |
| KV Namespaces | 512 | [a-zA-Z0-9_-] | Practical limit ~64 |
| Queues | 63 | [a-z0-9-] | Same as Workers |
Effective constraint: The naming scheme must use only [a-z0-9-] and must not exceed 63 characters for the most restrictive resource types (Workers, Pages, R2, Queues).
Why NanoID (Not ULID)
The previous proposal used the first 8 characters of a ULID — this is fundamentally broken:
| Problem | Detail |
|---|---|
| Zero randomness | A ULID's first 10 chars are entirely timestamp. Truncating to 8 gives no random bits. Two IDs created in the same millisecond share the same 8-char prefix. |
| Uppercase alphabet | ULIDs use Crockford Base32 ([0-9A-Z]). Cloudflare requires lowercase-only for Workers, Pages, and R2. |
| Misleading collision claims | The "1 in 281 trillion" resistance only applies to full 26-char ULIDs — not to 8-char prefixes. |
NanoID solves all of these:
- Pure randomness across every character — no timestamp prefix
- Custom alphabet support — use
[a-z0-9]to satisfy Cloudflare's lowercase constraint - Configurable length — tune collision resistance to scale requirements
Collision Analysis: Choosing ID Length
Using alphabet [a-z0-9] (36 characters):
| ID Length | Total Combinations | 50% Collision At | 10M IDs Collision Risk | Decision |
|---|---|---|---|---|
| 8 chars | 2.8 trillion | ~2M IDs | ~1.8% | Too risky at scale |
| 10 chars | 3.6 quadrillion | ~84M IDs | ~0.0014% | Recommended |
| 12 chars | 4.7 quintillion | ~3B IDs | ~0.000001% | Conservative option |
Recommendation: 10 characters (nanoid(10) with custom alphabet [a-z0-9]).
This allows Neutrino to support tens of millions of entities while keeping IDs concise enough to fit within Cloudflare naming limits.
NanoID Configuration
import { customAlphabet } from 'nanoid';
// Cloudflare-safe NanoID: lowercase alphanumeric only
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 10);
// Usage
const platformId = nanoid(); // e.g. "k3m9p2xw7q"
const tenantId = nanoid(); // e.g. "r8n4t6y1z5"Entity Hierarchy
Definitions
| Entity | Description | Has Own NanoID |
|---|---|---|
| Neutrino | The meta-platform itself. Deployed on its own Cloudflare account. Has its own platform ID. | Yes (nno prefix + 10-char id) |
| Platform | A client's top-level environment. One client = one platform. | Yes (10-char) |
| Tenant | A logical group within a platform (business unit, team, customer). | Yes (10-char) |
| Sub-Tenant | An optional nested group within a tenant. Has its own unique ID; parent mapping is tracked in the registry. | Yes (10-char) |
| Service | A specific Cloudflare resource provisioned for a tenant/sub-tenant. | Named, not ID-based |
| Environment | Deployment tier of a service. | dev / stg / prod |
Key Design Principle
The hierarchy is stored in the Neutrino Registry (D1 database), not encoded in the Cloudflare resource name. This means:
- Sub-tenants use their own unique ID in resource names — no parent-chaining in the name
- Names stay short and predictable regardless of nesting depth
- The registry is the source of truth for ownership and hierarchy
Naming Convention
Standard Format
[Updated for DNS architecture] The
\{entity-id\}segment has been replaced by\{stack-id\}in the current model.\{entity-id\}references in this document are considered legacy; new resources use\{stack-id\}. The staging suffix is now-stg(not-prod/-dev) and production resources carry no environment suffix.
{platform-id}-{stack-id}-{service}[-resourceType][-stg]| Segment | Length | Example |
|---|---|---|
platform-id | 10 chars | k3m9p2xw7q |
| separator | 1 char | - |
stack-id | 7–10 chars | default or r8n4t6y1z5 |
| separator | 1 char | - |
service | 3–8 chars | auth, db, storage |
-resourceType | optional | -db, -storage, -kv |
-stg | optional | staging only; absent = production |
| Total | ~27–40 chars | k3m9p2xw7q-default-auth (prod) or k3m9p2xw7q-default-auth-stg (staging) |
Maximum realistic length: ~40 chars — well within the 63-char Cloudflare limit.
Legacy entity-id format: The previous format
\{platform-id\}-\{entity-id\}-\{service\}-\{env\}(e.g.k3m9p2xw7q-r8n4t6y1z5-auth-prod) is deprecated. New resources use\{stack-id\}and omit the-prodsuffix. Existing resources retain their original names for stability.
Neutrino's Own Resource Format
Neutrino deploys its own infrastructure using the same convention. Its platform ID is reserved as nno (fixed 3-char prefix) combined with a generated ID:
nno-{nno-platform-id}-{service}[-stg]Example:
nno-k3m9p2xw7q-registry
nno-k3m9p2xw7q-registry-stg
nno-k3m9p2xw7q-iam
nno-k3m9p2xw7q-billingD1 Database Naming
D1 databases include an explicit -db segment to distinguish them from the Worker that owns them. The full pattern is:
nno-{platformId}-{service}-db[-stg]For NNO operator services (billing, registry, provisioning, stack-registry, IAM, gateway, cli-service), the nno- prefix always applies regardless of which client tenant the service is ultimately serving — because these are NNO-operated, not client-provisioned, resources:
nno-k3m9p2xw7q-billing-db
nno-k3m9p2xw7q-billing-db-stg
nno-k3m9p2xw7q-registry-db
nno-k3m9p2xw7q-registry-db-stgFor client-provisioned service databases the pattern follows the standard stack format with the -db segment:
{platformId}-{stackId}-{service}-db[-stg]For example:
k3m9p2xw7q-default-auth-db
k3m9p2xw7q-default-auth-db-stgNote: NNO operator services (billing, registry, provisioning, stack-registry, IAM, gateway, cli-service) always use
nno-\{platformId\}-\{service\}-db[-stg]. This rule applies even when the service is storing data on behalf of a specific client tenant — the resource is NNO-operated and must stay under the NNO namespace.
DNS hostname conventions: For the DNS hostname patterns that correspond to these CF resource names (e.g.
auth.svc.default.<pid>.nno.app), see dns-naming.md.
Service Abbreviations
| Service Type | Abbreviation | Resource |
|---|---|---|
| Authentication | auth | Cloudflare Worker |
| Frontend Portal | portal | Cloudflare Pages |
| API Worker | api | Cloudflare Worker |
| Database | db | Cloudflare D1 |
| Media Storage | media | Cloudflare R2 |
| Cache | cache | Cloudflare KV |
| Queue | queue | Cloudflare Queue |
email | Cloudflare Worker | |
| Registry | registry | Cloudflare Worker + D1 |
| IAM | iam | Cloudflare Worker |
Character Length Validation
Format: {10}-{10}-{8}-{4}
Chars: 10 + 1 + 10 + 1 + 8 + 1 + 4 = 35 chars
Format (short service + env): {10}-{10}-{3}-{3}
Chars: 10 + 1 + 10 + 1 + 3 + 1 + 3 = 29 chars| Resource | Limit | Our Max | Margin |
|---|---|---|---|
| Workers | 63 | 35 | +28 ✅ |
| Pages | 63 | 35 | +28 ✅ |
| D1 | 64 | 35 | +29 ✅ |
| R2 | 63 | 35 | +28 ✅ |
| KV | 512 | 35 | +477 ✅ |
| Queues | 63 | 35 | +28 ✅ |
Hierarchy Examples
Scenario: Neutrino Deploying Itself
Neutrino has its own platform ID generated once and stored permanently.
Platform: nno (Neutrino meta-platform)
Platform ID: nno-k3m9p2xw7q
Resources (production — no suffix):
nno-k3m9p2xw7q-registry ← Core registry Worker
nno-k3m9p2xw7q-registry-db ← Core registry D1 database
nno-k3m9p2xw7q-iam ← IAM service Worker
nno-k3m9p2xw7q-iam-db ← IAM D1 database
nno-k3m9p2xw7q-billing ← Billing service Worker
nno-k3m9p2xw7q-billing-db ← Billing D1 database
nno-k3m9p2xw7q-console ← Neutrino console (Pages)
Resources (staging — -stg suffix):
nno-k3m9p2xw7q-registry-stg
nno-k3m9p2xw7q-registry-db-stgScenario: Client Platform — Stack-Based Resources
Platform ID: k3m9p2xw7q (AcmeCorp)
Default stack (auto-created, auth always lives here):
k3m9p2xw7q-default-auth ← Auth Worker (prod)
k3m9p2xw7q-default-auth-db ← Auth D1 (prod)
k3m9p2xw7q-default-auth-stg ← Auth Worker (staging)
k3m9p2xw7q-default-auth-db-stg ← Auth D1 (staging)
Stack x7y8z9w0q1 (e.g. "Marketing"):
k3m9p2xw7q-x7y8z9w0q1-dashboard ← Pages project (prod)
k3m9p2xw7q-x7y8z9w0q1-dashboard-api ← API Worker (prod)
k3m9p2xw7q-x7y8z9w0q1-db ← Shared D1 (prod)
k3m9p2xw7q-x7y8z9w0q1-storage ← Shared R2 (prod)
k3m9p2xw7q-x7y8z9w0q1-dashboard-stg ← Pages project (staging)NNO Core Services: For the Neutrino Orchestration Service architecture (Registry, Provisioning, IAM, Billing, Gateway), see System Architecture — Section 3. Registry D1 Schema: For the authoritative D1 schema (entities, resources, secrets, stacks tables), see Registry Architecture.
KV Namespaces
Platform Status KV (Phase 2, NNO-level)
nno-\{nno-platform-id\}-platform-status-\{env\} — e.g., nno-k3m9p2xw7q-platform-status-prod
Contains keys platform:\{platformId\}:status with string values (active, suspended, pending_cancellation, cancelled, deleted). Used by Gateway for near-instant suspension enforcement. When a platform's status changes, Registry writes to this namespace; Gateway reads on every authenticated /api/* request. Gateway binding: NNO_PLATFORM_STATUS_KV.
Stack Resource Naming
When a Stack instance is provisioned, its shared Cloudflare resources follow a standardized segment order that aligns with the base naming convention (\{platformId\}-\{stackId\}-\{service\}[-resourceType][-stg]). The stackId takes the role of entityId and the resource type is appended as an optional segment:
{platformId}-{stackId}-{service}[-resourceType][-stg]| Resource | Pattern (prod) | Pattern (staging) | Example (prod) | Example (staging) |
|---|---|---|---|---|
| Shared D1 | \{platformId\}-\{stackId\}-db | \{platformId\}-\{stackId\}-db-stg | k3m9p2xw7q-saas-starter-db | k3m9p2xw7q-saas-starter-db-stg |
| Shared R2 | \{platformId\}-\{stackId\}-storage | \{platformId\}-\{stackId\}-storage-stg | k3m9p2xw7q-saas-starter-storage | k3m9p2xw7q-saas-starter-storage-stg |
| Shared KV | \{platformId\}-\{stackId\}-kv | \{platformId\}-\{stackId\}-kv-stg | k3m9p2xw7q-saas-starter-kv | k3m9p2xw7q-saas-starter-kv-stg |
| Stack Worker | \{platformId\}-\{stackId\}-orchestrator | \{platformId\}-\{stackId\}-orchestrator-stg | k3m9p2xw7q-saas-starter-orchestrator | k3m9p2xw7q-saas-starter-orchestrator-stg |
Key rule: production resources have no environment suffix. Staging resources end with -stg. There is no -prod or -dev suffix.
Legacy naming — Stack Registry D1: The
services/stack-registryD1 databases were provisioned before this naming convention was finalised and use a legacymarketplace-db-*prefix (e.g.,marketplace-db-dev,marketplace-db-stg,marketplace-db-prod). These names diverge from thenno-\{platformId\}-\{service\}-db-\{env\}convention. No rename is planned — the existing database IDs are stable references in wrangler config and CF dashboard.
Source of truth:
docs/architecture/concepts/stacks.md— Section 3 (Shared Resource Model) for prefix conventions within shared resources;docs/architecture/services/provisioning.md— Section 2.3a (PROVISION_STACK) for the provisioning step that creates these resources.
Naming Helper Library
The naming utilities live in packages/core/src/naming/ and are exported via the @neutrino-io/core/naming subpath. Import them as:
import {
generateId,
buildResourceName,
buildNnoResourceName,
buildLegacyResourceName,
parseResourceName,
parseNnoResourceName,
validateResourceName,
validateId,
} from '@neutrino-io/core/naming'Exported Types
import type { Environment, ValidationResult } from '@neutrino-io/core';
// Environment union (from packages/core/src/types/index.ts)
export type Environment = 'dev' | 'stg' | 'prod';
// Parts for a client platform resource name
export interface ResourceNameParts {
platformId: string;
stackId: string; // was entityId in legacy format
service: string;
staging?: boolean; // true = -stg suffix; omit or false = production (no suffix)
resourceType?: 'db' | 'storage' | 'kv' | 'queue';
}
// Parts for a Neutrino NNO-level resource name
export interface NnoResourceNameParts {
platformId: string;
service: string;
staging?: boolean; // true = -stg suffix; omit or false = production (no suffix)
resourceType?: 'db' | 'storage' | 'kv' | 'queue';
}
// Returned by parseResourceName (distinct from ResourceNameParts)
export interface ParsedResourceName {
platformId: string;
stackId: string;
service: string;
resourceType?: string;
isStaging: boolean;
}
// Returned by parseNnoResourceName (distinct from NnoResourceNameParts)
export interface ParsedNnoResourceName {
platformId: string;
service: string;
resourceType?: string;
isStaging: boolean;
}
/** @deprecated Use ResourceNameParts with stackId instead */
export interface LegacyResourceNameParts {
platformId: string;
entityId: string;
service: string;
environment: Environment;
}Exported Functions
| Function | Description |
|---|---|
generateId(): string | Generate a Cloudflare-safe NanoID (10 chars, [a-z0-9]). Use for platform IDs, tenant IDs, sub-tenant IDs. |
buildResourceName(parts: ResourceNameParts): string | Build a client resource name. Format: \{platformId\}-\{stackId\}-\{service\}[-resourceType][-stg]. Throws if the name exceeds 63 chars or contains invalid characters. |
buildNnoResourceName(parts: NnoResourceNameParts): string | Build an NNO-level resource name. Format: nno-\{platformId\}-\{service\}[-resourceType][-stg]. Throws if the name is invalid. |
parseResourceName(name: string): ParsedResourceName | null | Parse a client resource name back into its components. Returns null if the name does not match. |
parseNnoResourceName(name: string): ParsedNnoResourceName | null | Parse an NNO resource name. Returns null if the name does not match. |
validateResourceName(name: string): ValidationResult | Validate a name against CF constraints: max 63 chars, [a-z0-9-] only, no leading/trailing hyphens. |
validateId(id: string): ValidationResult | Validate a NanoID: exactly 10 chars, [a-z0-9] only. |
buildLegacyResourceName(parts: LegacyResourceNameParts): string | Deprecated. Build a legacy \{platformId\}-\{entityId\}-\{service\}-\{env\} name. Use buildResourceName with stackId for new code. |
Usage Examples
import { generateId, buildResourceName, buildNnoResourceName } from '@neutrino-io/core/naming';
// Generate a new platform ID
const platformId = generateId(); // e.g. 'k3m9p2xw7q'
// Build a client resource name (production — no suffix)
const dbName = buildResourceName({
platformId: 'k3m9p2xw7q',
stackId: 'default',
service: 'auth',
resourceType: 'db',
});
// => 'k3m9p2xw7q-default-auth-db'
// Build a client resource name (staging — -stg suffix)
const dbNameStg = buildResourceName({
platformId: 'k3m9p2xw7q',
stackId: 'default',
service: 'auth',
resourceType: 'db',
staging: true,
});
// => 'k3m9p2xw7q-default-auth-db-stg'
// Build an NNO-level resource name (production)
const registryDb = buildNnoResourceName({
platformId: 'k3m9p2xw7q',
service: 'registry',
resourceType: 'db',
});
// => 'nno-k3m9p2xw7q-registry-db'
// Build an NNO-level resource name (staging)
const registryDbStg = buildNnoResourceName({
platformId: 'k3m9p2xw7q',
service: 'registry',
resourceType: 'db',
staging: true,
});
// => 'nno-k3m9p2xw7q-registry-db-stg'Neutrino Wrangler Configuration
Neutrino's own services follow the same convention. Example services/registry/wrangler.toml:
# ============================================================================
# Neutrino Registry Service
# Platform: Neutrino Meta-Platform (nno)
# Platform ID: k3m9p2xw7q (generated once, stored permanently)
# Format: nno-{platformId}-{service}-{env}
# ============================================================================
name = "nno-k3m9p2xw7q-registry"
main = "src/index.ts"
compatibility_date = "2024-09-13"
compatibility_flags = ["nodejs_compat"]
[[d1_databases]]
binding = "DB"
database_name = "nno-k3m9p2xw7q-registry-db"
database_id = "local-dev-db"
migrations_dir = "migrations"
[vars]
PLATFORM_ID = "k3m9p2xw7q"
SERVICE_NAME = "registry"
ENVIRONMENT = "prod"
[env.stg]
name = "nno-k3m9p2xw7q-registry-stg"
[[env.stg.d1_databases]]
binding = "DB"
database_name = "nno-k3m9p2xw7q-registry-db-stg"
database_id = "REPLACE_WITH_REAL_ID"
[env.stg.vars]
PLATFORM_ID = "k3m9p2xw7q"
SERVICE_NAME = "registry"
ENVIRONMENT = "stg"Client Platform Wrangler Configuration
For a provisioned client platform (k3m9p2xw7q, tenant r8n4t6y1z5):
# Generated by Neutrino Orchestration Service
# Platform: k3m9p2xw7q | Stack: default | Service: auth
name = "k3m9p2xw7q-default-auth"
main = "src/index.ts"
compatibility_date = "2024-09-13"
[env.stg]
name = "k3m9p2xw7q-default-auth-stg"Summary
| Dimension | Decision |
|---|---|
| ID Generator | NanoID, custom alphabet [a-z0-9], 10 characters |
| Naming Format | \{platform-10\}-\{stack-id\}-\{service\}[-resourceType][-stg] |
| Hierarchy | Platform → Tenant → Stack → apps + services (tracked in Registry, not encoded in name) |
| Neutrino's own name | nno-\{platformId\}-\{service\}[-stg] |
| Max resource name | ~40 chars (63-char limit respected by wide margin) |
| Collision risk at 10M IDs | ~0.0014% (10-char NanoID, [a-z0-9]) |
| Core service | Neutrino Orchestration: Registry + Provisioning + IAM + Billing |
| Source of truth for hierarchy | D1 Registry database (not encoded in names) |
| Legacy format | \{platform-id\}-\{entity-id\}-\{service\}-\{env\} (deprecated — entity-id replaced by stack-id; -prod suffix removed) |
| DNS hostname conventions | See dns-naming.md |
Revised: 2026-03-30
Status: Approved for Implementation — Complete
Platform ID: k3m9p2xw7q (generated)
Core Package: packages/core (@neutrino-io/core) implemented and deployed