Designing a Multi-Tenant PaaS
with Cloudflare at the Edge

KamuiDash's edge architecture for a multi-tenant PaaS. Cloudflare handles domain resolution, dynamic-request relay, and origin protection; Workers, KV, and Tunnel divide the work.

KamuiDash receives tenant applications and custom domains through one Cloudflare edge.
This article follows a request from the public entry point to a tenant application's response.

From the public edge to a tenant application

Every HTTP request first reaches a Cloudflare Worker. The Worker resolves the target application from the Host. Dynamic requests then pass through Cloudflare Tunnel to an internal router and the tenant application in the cluster.

The Worker owns domain resolution and path selection at the entry point. Only requests that need application execution reach the cluster.

From edge to destination
UserBrowser / API client
HTTPS
CLOUDFLARE EDGE
WorkersDomain resolution and path selection
Edge responseSome content completes here
TunnelAn established private path
Dynamic request
PRIVATE ORIGIN
Internal routerRoutes to the target tenant
Tenant appReturns dynamic content
Only dynamic requests move through Tunnel to the private origin.

1. Resolve the Host in the Worker

The Worker normalizes the received Host, then reads a KV mapping between domains and internal applications. Custom domains and platform subdomains are both inputs to the same question: which application does this request identify? An unknown external domain returns a 404 at the Worker rather than entering the cluster.

After identifying an application, the Worker checks its serving mode. When the response does not finish at the edge, it reads the dynamic destination and forwards through Tunnel. The KV data is separated by purpose.

Routing information read by the Worker
Domain mapping        Host            → internal application
Destination registry  internal app    → dynamic application destination

Domain mappings and dynamic destinations change at different rates and tolerate different propagation windows, so they are not kept in one record.

Request branching
  1. 01Resolve the HostMap a custom domain to an internal application
  2. 02Check serving modeOnly dynamic requests enter the cluster
  3. 03Resolve the destinationSelect a Tunnel path from application placement
  4. 04Normalize failures at the edgeKeep timeouts, upstream errors, and tracing consistent
The Host identifies an application; only dynamic requests are passed to Tunnel.

2. Dynamic requests continue through Tunnel and the internal router

When a response does not finish at the edge, the Worker reads the application-to-destination mapping and selects a Tunnel path. It attaches the resolved application identifier and routing context before forwarding. Any client-supplied values with the same purpose are replaced by the Worker's resolved values.

For each forward, the Worker builds a new upstream request from the received headers. It explicitly sets the Host and routing context from its resolution, and adds a request ID. Tenant selection is therefore not delegated to an arbitrary value sent by the client.

The Tunnel connector establishes an outbound connection to Cloudflare, so the cluster does not expose an internet-reachable receiving endpoint. The internal router receives the resolved context on the other end of the Tunnel and forwards to the target tenant application.

The upstream fetch is bounded with an AbortController. Connection failures, timeouts, and upstream server errors become gateway errors at the entry point. Internal routing information is removed from responses, and a dynamic response without Cache-Control receives a non-shared-cache default. WebSocket upgrades pass through separately from normal HTTP responses.

Trust boundary for a dynamic request
PUBLICClientHost / Path / Request headers
Receive
EDGEWorkerNormalize Host, read KV, rebuild upstream request
Resolved context
PRIVATE PATHTunnelAn outbound established connection
Connector only
CLUSTERInternal routerForwards to target tenant
Client input is not used directly for routing; the Worker passes resolved context through Tunnel to the internal router.

3. Limit the entry point after Tunnel too

The internal router uses routing information supplied by the Worker, so it accepts only requests that arrived through Tunnel. KamuiDash applies a NetworkPolicy that limits ingress to the internal router to Tunnel connector pods.

The policy permits the Tunnel connector alone as a source. This closes a path where another workload in the same cluster could reach the router directly and forge context that should have been resolved by the Worker. Host resolution at the public edge and source restriction in the cluster sit at opposite ends of the same request path.

4. What changes during domain registration and deployment

When adding a custom domain, KamuiDash completes ownership validation and certificate issuance through Cloudflare's custom-domain capability, then registers the mapping between the domain and the internal application. Removal releases that mapping and the domain-side state in sequence.

Moving a dynamic application changes its destination-registry mapping. Domain mappings and destinations change at different rates, so they use separate cache durations.

KV is not a store where a write immediately clears every edge cache: a missing result can persist briefly too. Registration and movement are tracked through DNS, certificates, and KV propagation. The Worker writes request IDs and edge tracing data to structured logs while excluding credentials and cookies, allowing the same request to be traced through Tunnel, the internal router, and the tenant application.

When a configuration change reaches the request path
Add a custom domain
Ownership and certificateUpdate domain mappingKV cache propagationWorker resolves the new Host
Move a dynamic application
Prepare destinationUpdate destination registryKV cache propagationWorker selects the new path
Registration and movement complete when the edge can resolve the new mapping, not merely when KV has been written.

What Cloudflare handles in this path

For custom domains, Cloudflare handles ownership validation and certificate issuance and renewal. KamuiDash retains the mapping from a domain to a tenant application, without a separate certificate and public-entry workflow for every domain.

The Worker is the shared execution point for every public request. It centralizes Host resolution, upstream request construction, error normalization, and request-ID creation instead of requiring each tenant application to implement the same entry logic.

With Tunnel, connectors in the cluster establish outbound connections. Tenant applications and the internal router do not need public receiving endpoints, while KamuiDash retains the NetworkPolicy that limits allowed sources.

Summary

KamuiDash centralizes three things at the Cloudflare entry point.

Putting Cloudflare at the entry point lets KamuiDash avoid a separate public path for every tenant while keeping the Worker, Tunnel, and internal router as distinct responsibilities.

For KamuiDash's broader design philosophy and the basic GitHub-to-deployment flow, see Why Use KamuiDash.