Prerequisites
You need:- a Cloudflare account with Workers, D1, KV, R2, Artifacts, Durable Objects, and Zero Trust;
- a Cloudflare zone for the app, API, and usercontent custom domains;
- Node,
pnpm, and authenticatedwrangleraccess to your Cloudflare account; and - a clean checkout of the
container-v0.2.0source tag.
/healthz
returns ok: true.
Install the locked dependencies from the repository root:
Understand the three origins
The app and API origins route to the same app Worker. Protect only the browser
app origin with Access. You must also deploy the usercontent Worker. If a raw
URL reaches the app Worker, it returns the browser app HTML instead of the
requested file.
The self-host templates do not include the rate limits used by hosted
Workspaces. If the public API origin allows anonymous creation, add a Cloudflare
WAF rule to its custom domain to limit abuse.
Create the shared resources
Run every command block on this page from the repository root. The examples use the resource names shown below. If you change a name, update every later command and both Worker configs. Record resource IDs outside the repository.Configure and deploy the app Worker
1
Copy the app template
2
Set the app bindings
Fill in the app Worker name, D1 ID, KV ID, both R2 bucket names, Artifacts
namespace, source commit, app origin, API origin, usercontent origin,
instance organization ID, and organization name. Set
database_name to the
exact D1 name you created, such as workspaces-db, in addition to setting
database_id. Keep separate custom-domain routes for the app and API
origins.Set AUTH_MODE to cloudflare_access. Add the Access team domain and
application audience from Configure Cloudflare
Access.3
Apply D1 migrations
4
Set the app secrets
5
Build the browser app
From the repository root, run:
6
Deploy the app Worker
Configure and deploy the usercontent Worker
1
Copy the usercontent template
2
Use the same data resources
Set a different Worker name and a different custom domain. Copy the exact
D1 name, D1 ID, KV ID, both R2 bucket names, Artifacts namespace, and source
commit used by the app Worker. The
database_name and database_id values
must match the app Worker config.Do not add app secrets, Durable Objects, sessions, or D1 migrations to this
Worker.3
Deploy the usercontent Worker
4
Set the usercontent origin
Set
USERCONTENT_ORIGIN in the app Worker config to the usercontent origin.
Redeploy the app Worker so new raw links use that host.Verify the deployment
- Request
/healthzon the app, API, and usercontent origins. Confirm all three report the same source commit. - Complete Cloudflare Access setup and sign in on the app origin.
- Create a Markdown document and copy its raw link.
- Request the living and exact-version raw URLs from the usercontent origin.
Both should return
200withtext/markdownand the document bytes. - Confirm the same raw path on the app origin does not serve the file.
- Create a test API key after sign-in. Use it for a request to the public API
origin, revoke it, and confirm the next request returns
401 unauthorized. - Open the document in two browser windows and confirm live editing connects.
Optional product telemetry
The self-host template has noTELEMETRY binding, so Workspaces records no
product telemetry and reports nothing to Plannotator. To opt in, add an
Analytics Engine dataset binding named TELEMETRY to the app Worker. The data
then stays in your Cloudflare account. It contains event names and broad
categories, not account IDs, workspace IDs, document IDs, paths, or URLs.
Reviewed July 22, 2026. Maintained by the Plannotator documentation team.