Deploy to Cloudflare Workers
On Cloudflare the whole site runs as one Worker, with no server for you to look after. This page takes you from the download to a live site. It is for whoever has a Cloudflare account and is comfortable with a terminal. The free plan is enough for a choir-sized site.
You need:
- a Cloudflare account, and a domain whose DNS zone is on that account if you want your own address;
- Node.js 22.13 or newer on your own computer, to run
npx wrangler(Wrangler is Cloudflare's command-line tool;npxfetches it for you, so there is nothing to install); - the download and a licence key.
Work from the download, unpacked. Never run npm run deploy:cloudflare or any build command: those need the product's source code, which the download does not contain. The download's website is already built, and npx wrangler deploy uploads it as it is.
1. Unpack
mkdir choir-worker
tar -xzf choirmaster-cms-1.6.12.tar.gz -C choir-worker
cd choir-worker
2. Log in to Cloudflare
npx wrangler login
A browser window opens to let Wrangler use your account. To find your account's id for later:
npx wrangler whoami
3. Create the resources
npx wrangler d1 create choir-db
npx wrangler r2 bucket create choir-public
npx wrangler r2 bucket create choir-private
npx wrangler kv namespace create SESSIONS
| Resource | What it is for |
|---|---|
D1 database choir-db | All the site's records. |
R2 bucket choir-public | Public files: gallery, posters, uploaded images. |
R2 bucket choir-private | Member files and rehearsal tracks. Never give this one a public address. |
KV namespace SESSIONS | Login sessions. Optional: skip it and use the database for sessions (see below). |
The d1 create command prints a database_id, and kv namespace create prints an id. Copy both. If Cloudflare says R2 is not enabled, enable it for your account in the Cloudflare dashboard first.
4. Fill in wrangler.toml
cp wrangler.toml.example wrangler.toml
Edit wrangler.toml:
- uncomment
account_idand set your account id; - set
database_idunder[[d1_databases]]to the one printed in step 3; - set
idunder[[kv_namespaces]]to the KV id; - uncomment
routesand set your domain:routes = [{ pattern = "choir.example.org", custom_domain = true }]; - in
[vars], setSITE_URLtohttps://choir.example.org, andEMAIL_PROVIDERandEMAIL_FROMfor your email service.
Leave workers_dev = false and preview_urls = false alone: without them a deploy also publishes the site, backed by your real data, at a workers.dev address. Leave the [assets] section as it is too: its binding = "ASSETS" and run_worker_first lines are what put your choir's name into the page for link previews and search engines, and they only work as a pair. See Your choir's name in the page.
If you skipped KV, delete the [[kv_namespaces]] block and add SESSION_STORE = "database" to [vars].
Every line is explained in wrangler.toml explained, and the custom domain in Cloudflare: your domain and public files. smtp is the one email provider that does not work on Workers.
5. Create the database tables
The schema is never applied automatically on Cloudflare. Apply it yourself:
npx wrangler d1 execute choir-db --remote --file=./server/db/schema/sqlite.sql
It only creates what is missing, so it is safe to run again. You will run it again for each update.
6. Set the secrets
Secrets are kept by Cloudflare, not in your files:
npx wrangler secret put ADMIN_USERNAME
npx wrangler secret put ADMIN_PASSWORD
npx wrangler secret put LICENSE_KEY
Wrangler asks you to type each value. Then the secrets for your email service, for example Amazon SES:
npx wrangler secret put AWS_ACCESS_KEY_ID
npx wrangler secret put AWS_SECRET_ACCESS_KEY
or RESEND_API_KEY, SENDGRID_API_KEY and the like: see Email: what the site sends and how to choose a provider.
| Secret | When |
|---|---|
ADMIN_USERNAME, ADMIN_PASSWORD | Always, unless you use ADMIN_AUTH = "table" and add admins another way. |
LICENSE_KEY | To be told about new versions. |
| Your email service's keys | Always, with a real email service. |
CAPTCHA_SECRET_KEY | If CAPTCHA_PROVIDER is set. |
ANTHROPIC_API_KEY | Only for the optional writing assistant in the guided setup. |
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY | Only if you use STORAGE_PROVIDER = "s3". |
If Wrangler says there is no Worker of that name yet and asks whether to create one, say yes. The deploy in the next step then fills it in.
7. Deploy
npx wrangler deploy
Wrangler bundles the server, uploads the built website in dist/ as the Worker's static assets, and attaches your domain. With a custom_domain route, Cloudflare creates the DNS record for you.
8. Log in
Open https://choir.example.org/admin/login and log in with the username and password you set as secrets. The first time, the site takes you through the guided setup: see Log in to the admin panel and Set up your site step by step. To give each admin their own login instead, see Add the first admin, and get back in when locked out.
Then work through After installing: first login and go-live checklist.
What is different on Workers
| On Cloudflare | |
|---|---|
| Database schema | You apply it with wrangler d1 execute, now and after each update. |
| Updates | Always by hand. The admin panel shows the steps. See Update by hand. |
| Everything except SMTP. | |
| Secrets | wrangler secret put and [vars] only. NAME_FILE settings and secrets managers do not apply. |
| Database | D1 only. |
| Files | R2, or any S3-compatible store with STORAGE_PROVIDER = "s3". Not local disk. |
| Limits | See Cloudflare: limits and logs. |
Your wrangler.toml | Yours. An update never overwrites it, because the download only has wrangler.toml.example. When a release needs something new in it, its release notes say so: see Update by hand. |
| Pages | Every page view runs the Worker and reads the site's settings from D1 once, so that the page carries your choir's name. See Your choir's name in the page. |
If something goes wrong
| What you see | What to do |
|---|---|
PUBLIC_BUCKET and PRIVATE_BUCKET R2 bindings are required (or set STORAGE_PROVIDER=s3) | The R2 blocks are missing or misnamed in wrangler.toml. The binding names must be exactly PUBLIC_BUCKET and PRIVATE_BUCKET. |
| Errors mentioning a missing table | Step 5 was not run, or was run without --remote. |
| You cannot log in | The ADMIN_USERNAME and ADMIN_PASSWORD secrets are not set. Check with npx wrangler secret list. |
[config] lines | See Cloudflare: limits and logs for how to read the log. |
Every page shows {"success":false,"error":"Not Found"} | The [assets] section has the new run_worker_first but no binding = "ASSETS". Copy the section from wrangler.toml.example and deploy again. |
| A shared link to the site is titled "Choir" | The [assets] section is an older one. See Your choir's name in the page. |
| The pages appear but are empty of content, and nobody can log in | The Worker could not start, so pages are served as built and the API is down. npx wrangler tail says why. |