Cloudflare reference deployment
Deployment profiles
The minimum production topology is two Workers: the Astro site and tenant workspace. The plugin sandbox, public MCP, and private MCP are optional Workers, not prerequisites for a basic marketing site. The CLI groups them into core (2), plugins (3), ai (4), and full (5) profiles; the AI profile deliberately keeps the physically separated public/private MCP pair together:
pnpm wise-wig cloudflare plan --profile core --cwd examples/morning-bell-coffee
pnpm --filter @wise-wig/morning-bell-coffee build
pnpm wise-wig cloudflare prepare --profile core --cwd examples/morning-bell-coffee
pnpm wise-wig doctor --profile core --cwd examples/morning-bell-coffee
pnpm --filter @wise-wig/tenant-workspace-worker exec wrangler deploy --dry-run --config wrangler.wise-wig-core.jsonc
pnpm --dir examples/morning-bell-coffee exec wrangler deploy --dry-run --config dist/server/wrangler.wise-wig-core.json
Preparation removes inactive optional service bindings from generated deployment configuration. This matters because a Cloudflare service binding targets a Worker already present on the same account; merely avoiding calls to a missing binding does not make that target optional. The managed hosting deployer remains separate operator infrastructure and is omitted from self-hosted profile output.
The complete production-shaped Morning Bell reference is five framework Workers plus an optional operator-owned hosting deployer service:
@wise-wig/tenant-workspace-workerexportsTenantWorkspaceDOand has no public route. One deterministic SQLite-backed object coordinates each agency tenant; the Worker also consumes publish and dead-letter queues to refresh public indexes/caches and dispatch isolated after-publish hooks.@wise-wig/morning-bell-coffeeis the Astro site Worker. It binds D1 as the routing/membership control plane, the remote tenant workspace namespace, R2 asMEDIA, and a required signed-session secret. Astro reservesASSETSfor built static files.@wise-wig/plugin-sandbox-workeris non-public and reachable only through the site’sPLUGIN_SANDBOXservice binding. Its sole storage binding is a digest-addressed artifact Durable Object; do not add tenant D1/R2/session bindings.@wise-wig/public-mcp-workeris an anonymous Streamable HTTP MCP service with only sanitizedPUBLIC_CONTENTKV plus its own MCP/rate Durable Objects.@wise-wig/private-mcp-workeris the OAuth-protected MCP service. It binds identity D1 and the remote tenant workspace; it is never mounted in the public MCP process.
Create wise-wig-publish-events and wise-wig-publish-dlq, then deploy the workspace and plugin sandbox Workers first, apply the D1 migrations, configure secrets, and deploy the Astro/MCP Workers. The committed Wrangler configurations and generated types are checked during pnpm check; all Workers are bundled during pnpm build. A site deployment can be validated without external changes:
pnpm --filter @wise-wig/tenant-workspace-worker build
pnpm --filter @wise-wig/plugin-sandbox-worker build
pnpm --filter @wise-wig/morning-bell-coffee build
pnpm --filter @wise-wig/public-mcp-worker build
pnpm --filter @wise-wig/private-mcp-worker build
cd examples/morning-bell-coffee
pnpm exec wrangler deploy --dry-run --config dist/server/wrangler.json
Set SESSION_SECRET, OAUTH_CLIENT_SECRET, and PROVISIONING_SECRET with interactive wrangler secret put commands; never put them in Wrangler vars, shell history, or version control. The private MCP Worker has its own SESSION_SECRET and OAUTH_CLIENT_SECRET; do not reuse production secrets across Workers. Replace the D1/KV identifiers, OAuth provider placeholders, redirect URIs, MEDIA_PUBLIC_BASE_URL, and PLUGIN_TRUST_KEYS; provision the wise-wig-media R2 bucket and attach that base URL as its public custom domain before a real deployment. Plugin trust keys are public JWKs but remain environment-specific configuration.
Apply all infra/d1 migrations. The bearer-protected POST /api/admin/provision route then idempotently creates and verifies the tenant, site, domain, invited external identity, owner memberships, initial document, and theme. INSERT OR IGNORE never hides a conflicting hostname or identity mapping: the provisioner reads both back and fails with SITE_PROVISION_CONFLICT. The tenant object initializes matching draft/published snapshots so the first public request is valid and the invited owner can immediately authenticate.
Request flow
Ordinary custom-domain GETs check a named Cloudflare cache before Astro rendering. Cache hits avoid D1, authentication, and Durable Object work. Cache misses resolve only verified D1 domains and load the published document from the tenant object. Query-bearing, preview, edit, and mutation requests bypass this public cache.
Preview/edit requests validate the signed cookie against the required secret, resolve authoritative tenant/site membership through D1, and load draft state. Section, variant, structure, and publish endpoints enforce exact origin, optimistic document versions, guarded permissions, durable idempotency claims, and audit records before invoking tenant RPC.
Localhost deliberately retains the in-memory extension POC. The fixed local handoff endpoint returns 404 on a production/custom hostname and cannot mint a production signed session.
Deployment status
All five framework Workers build and dry-run with their declared bindings. Provisioning, OAuth, durable edit/publish, media, CMS resources, signed declarative plugins, physically isolated MCP services, recovery operations, and managed-host records are wired. Managed installations bind HOSTING_DEPLOYER to an operator-controlled Worker that owns provider credentials and resolves Secrets Store references; self-hosted installations may omit or replace that adapter. Deployed account smoke tests and real identity/provider configuration remain environment-specific release checks.