NNO Docs
ArchitectureCross cutting

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 / prod

Neutrino 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 TypeMax LengthAllowed CharactersNotes
Workers63[a-z0-9-]No uppercase, no underscore
Pages Projects63[a-z0-9-]No uppercase, no underscore
D1 Databases64[a-zA-Z0-9_-]Slightly more permissive
R2 Buckets63[a-z0-9-]S3-compatible, lowercase only
KV Namespaces512[a-zA-Z0-9_-]Practical limit ~64
Queues63[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:

ProblemDetail
Zero randomnessA 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 alphabetULIDs use Crockford Base32 ([0-9A-Z]). Cloudflare requires lowercase-only for Workers, Pages, and R2.
Misleading collision claimsThe "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 LengthTotal Combinations50% Collision At10M IDs Collision RiskDecision
8 chars2.8 trillion~2M IDs~1.8%Too risky at scale
10 chars3.6 quadrillion~84M IDs~0.0014%Recommended
12 chars4.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

EntityDescriptionHas Own NanoID
NeutrinoThe meta-platform itself. Deployed on its own Cloudflare account. Has its own platform ID.Yes (nno prefix + 10-char id)
PlatformA client's top-level environment. One client = one platform.Yes (10-char)
TenantA logical group within a platform (business unit, team, customer).Yes (10-char)
Sub-TenantAn optional nested group within a tenant. Has its own unique ID; parent mapping is tracked in the registry.Yes (10-char)
ServiceA specific Cloudflare resource provisioned for a tenant/sub-tenant.Named, not ID-based
EnvironmentDeployment 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]
SegmentLengthExample
platform-id10 charsk3m9p2xw7q
separator1 char-
stack-id7–10 charsdefault or r8n4t6y1z5
separator1 char-
service3–8 charsauth, db, storage
-resourceTypeoptional-db, -storage, -kv
-stgoptionalstaging only; absent = production
Total~27–40 charsk3m9p2xw7q-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 -prod suffix. 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-billing

D1 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-stg

For 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-stg

Note: 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 TypeAbbreviationResource
AuthenticationauthCloudflare Worker
Frontend PortalportalCloudflare Pages
API WorkerapiCloudflare Worker
DatabasedbCloudflare D1
Media StoragemediaCloudflare R2
CachecacheCloudflare KV
QueuequeueCloudflare Queue
EmailemailCloudflare Worker
RegistryregistryCloudflare Worker + D1
IAMiamCloudflare 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
ResourceLimitOur MaxMargin
Workers6335+28
Pages6335+28
D16435+29
R26335+28
KV51235+477
Queues6335+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-stg

Scenario: 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]
ResourcePattern (prod)Pattern (staging)Example (prod)Example (staging)
Shared D1\{platformId\}-\{stackId\}-db\{platformId\}-\{stackId\}-db-stgk3m9p2xw7q-saas-starter-dbk3m9p2xw7q-saas-starter-db-stg
Shared R2\{platformId\}-\{stackId\}-storage\{platformId\}-\{stackId\}-storage-stgk3m9p2xw7q-saas-starter-storagek3m9p2xw7q-saas-starter-storage-stg
Shared KV\{platformId\}-\{stackId\}-kv\{platformId\}-\{stackId\}-kv-stgk3m9p2xw7q-saas-starter-kvk3m9p2xw7q-saas-starter-kv-stg
Stack Worker\{platformId\}-\{stackId\}-orchestrator\{platformId\}-\{stackId\}-orchestrator-stgk3m9p2xw7q-saas-starter-orchestratork3m9p2xw7q-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-registry D1 databases were provisioned before this naming convention was finalised and use a legacy marketplace-db-* prefix (e.g., marketplace-db-dev, marketplace-db-stg, marketplace-db-prod). These names diverge from the nno-\{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

FunctionDescription
generateId(): stringGenerate a Cloudflare-safe NanoID (10 chars, [a-z0-9]). Use for platform IDs, tenant IDs, sub-tenant IDs.
buildResourceName(parts: ResourceNameParts): stringBuild a client resource name. Format: \{platformId\}-\{stackId\}-\{service\}[-resourceType][-stg]. Throws if the name exceeds 63 chars or contains invalid characters.
buildNnoResourceName(parts: NnoResourceNameParts): stringBuild an NNO-level resource name. Format: nno-\{platformId\}-\{service\}[-resourceType][-stg]. Throws if the name is invalid.
parseResourceName(name: string): ParsedResourceName | nullParse a client resource name back into its components. Returns null if the name does not match.
parseNnoResourceName(name: string): ParsedNnoResourceName | nullParse an NNO resource name. Returns null if the name does not match.
validateResourceName(name: string): ValidationResultValidate a name against CF constraints: max 63 chars, [a-z0-9-] only, no leading/trailing hyphens.
validateId(id: string): ValidationResultValidate a NanoID: exactly 10 chars, [a-z0-9] only.
buildLegacyResourceName(parts: LegacyResourceNameParts): stringDeprecated. 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

DimensionDecision
ID GeneratorNanoID, custom alphabet [a-z0-9], 10 characters
Naming Format\{platform-10\}-\{stack-id\}-\{service\}[-resourceType][-stg]
HierarchyPlatform → Tenant → Stack → apps + services (tracked in Registry, not encoded in name)
Neutrino's own namenno-\{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 serviceNeutrino Orchestration: Registry + Provisioning + IAM + Billing
Source of truth for hierarchyD1 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 conventionsSee 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

On this page