Deploy
Charcha deploys to your own Cloudflare account. Cloudflare clones the repository into
your GitHub or GitLab account, creates the D1 database, asks you for the secrets below,
builds, and deploys. What you end up with is a Worker on
your-worker.your-subdomain.workers.dev and a repository of your own that you can keep
developing from. Later pushes to it redeploy.
The button lives in the
README, and points at
https://deploy.workers.cloudflare.com/?url=https://github.com/withsetu/charcha.
The form asks for two secrets
Section titled “The form asks for two secrets”Both want a real value, and any value you invent for either is safe.
| Secret | What it is |
|---|---|
CHARCHA_DASHBOARD_PASSWORD |
The only credential for the moderation dashboard at /admin. No account, no reset, no second user. Generate it with openssl rand -base64 24. Unset, the dashboard refuses every request including its own login. |
IP_HASH_SECRET |
The HMAC key that turns a commenter’s IP into the identifier the per-IP spam rate limit counts. Any long random string will do, say openssl rand -hex 32. Unset, no IP is stored at all and the per-IP half of the rate limit abstains. |
Nothing else is on that form, and the reason is worth thirty seconds of your time. Cloudflare’s deploy form requires a value in every field it shows, with no way to mark one optional. For a Turnstile secret that would be a trap rather than an inconvenience: the only thing you could do without a real value is invent one, an invented Turnstile secret matches no widget, and every comment on your site would then be held or refused, quietly, until you thought to look at that field again. So the optional secrets are not collected during deploy. You set them afterwards, when you actually want them.
The secrets you set afterwards
Section titled “The secrets you set afterwards”Six names, added one at a time in the Cloudflare dashboard under your Worker’s Settings, then Variables and Secrets. Setting a secret has the click path and the terminal equivalent for anyone who would rather type. Each takes effect on the next request, and there is nothing to redeploy.
| Name | Turns on |
|---|---|
TURNSTILE_SECRET_KEY |
The bot check |
RESEND_API_KEY |
Email when a comment arrives |
CHARCHA_NOTIFY_FROM |
Email, with the two either side of it |
CHARCHA_NOTIFY_TO |
Email, with the two above |
AKISMET_API_KEY |
Third-party spam checking, with the one below it |
CHARCHA_SITE_URL |
Third-party spam checking, with the one above it |
Turnstile is the invisible bot check. Its secret key goes here, its sitekey goes on
your own page as data-turnstile-sitekey, and you want both or neither. There is a
longer version of that warning, and it is
worth reading before you set one half.
The three email secrets work together or the feature stays off. A key with no recipient
has nowhere to send, and Charcha holds no owner address anywhere in its schema to guess
one from. CHARCHA_NOTIFY_FROM has to be on a domain verified in Resend under the same
account as the key. Turning this on sends the commenter’s display name and an excerpt
of their comment to Resend, who are a third party. It does not send their email address
or anything derived from their IP.
The two Akismet values also work together or not at all, and they are the one thing here that changes what your readers are told about. Setting them sends every comment that gets that far to Automattic, IP address included. Read Third-party spam checking before you set either, since it lists every field that goes and has the paragraph you owe your privacy notice.
Your dashboard’s Setup tab tells you which of the first four the Worker currently has,
plus IP_HASH_SECRET, as plain yes or no. The two Akismet values are not on it.
Allowed origins is a setting, not a secret
Section titled “Allowed origins is a setting, not a secret”There is no ALLOWED_ORIGINS environment variable to set. The list lives in the
database and you edit it at /admin, under Allowed origins.
It is the step a new deployment most often misses, and when it is missing the failure is total:
- On
POST /comments, a page on an unlisted origin getsHTTP 403 That origin is not allowed to comment on this site. - On
GET /comments, the Worker still answers, but without theAccess-Control-Allow-Originheader, so the browser throws the response away and the comments never render.
Add your site scheme first, with no trailing path: https://example.com. One per line.
The list holds at most 20 origins and 2,048 characters in total.
Your Worker’s own address is always allowed, without being listed and without anything being written to the database, which is what makes a fresh deployment usable before you have touched a setting. Your site is a different address, so it has to be added.
This is not Turnstile’s Hostname Management screen, which governs where the Turnstile widget may render and has no bearing here. The two are easy to confuse.
Migrations run before the deploy
Section titled “Migrations run before the deploy”Cloudflare creates the D1 database and does not migrate it, so the repository’s deploy
script does:
wrangler d1 migrations apply DB --remote && wrangler deployThat order and that && matter. A failed migration gives you a red build rather than a
live Worker querying an empty database. The target is the binding DB rather than the
database name, because the deploy form lets you rename the database.
After it deploys
Section titled “After it deploys”Open / and you get a short page saying Charcha is running. That is the whole of what
it says. The address is public and nobody has to sign in to reach it, so the page
reports no database state, no configuration, no comment counts, and not even where the
dashboard is.
Then:
- Sign in at
/admin. The password isCHARCHA_DASHBOARD_PASSWORD. - Add your site under Allowed origins.
- Paste the two lines into your page.
GET /health is the machine-readable check. See
Health and monitoring.
Deploying from a terminal instead
Section titled “Deploying from a terminal instead”pnpm installpnpm wrangler d1 create charcha # then put the returned id in wrangler.jsoncpnpm run deploy # applies migrations, then deployspnpm wrangler secret put CHARCHA_DASHBOARD_PASSWORDpnpm run deploy, not pnpm deploy. The latter is pnpm’s own built-in
workspace-deploy command, which shadows the script and does something else entirely.
Cloudflare pre-populates a build’s deploy command from package.json, so this is also
the first thing to check if a Workers Build fails while printing pnpm’s usage text. Set
the deploy command to pnpm run deploy under Settings, Build.
Two failures that are not your mistake
Section titled “Two failures that are not your mistake”Both are worth knowing about before you read one as something you did.
The build token and D1. wrangler d1 migrations apply exits non-zero with no
useful output when its token lacks D1 permissions, which is
a known wrangler issue. The
token Workers Builds documents creating for itself carries Account Settings (read),
Workers Scripts (edit), Workers KV (edit) and Workers R2 (edit), and D1 is not on that
list. If the migration step is what failed, add D1 (edit) to the token, or apply the
migrations once from a terminal with pnpm db:migrate:remote.
The ratelimits binding on the Workers Free plan. Charcha uses the Workers Rate
Limiting binding to bound brute-force attempts against the dashboard login. Whether
that binding is available on Free is documented nowhere: not the binding reference, not
the bindings index, not the pricing page’s Free-against-Paid table, not the GA
changelog. All four were checked on 2026-07-25 and the silence is real rather than
unread. If the deploy fails mentioning it, deleting the ratelimits block from
wrangler.jsonc makes the deploy succeed, and makes the login throttle refuse every
attempt, so your dashboard stays shut. Comments carry on being accepted and queued
throughout.
The package manager, if a build goes strange
Section titled “The package manager, if a build goes strange”The Cloudflare build image ships pnpm 10.x, and the repository pins pnpm 10.x to match. Keep those on the same major. pnpm does not fail on a lockfile it considers incompatible, it prints a warning and resolves fresh, which gives you a green build that installed a dependency tree nobody locked or tested.
Workers Builds takes its pnpm version from the build image or from a PNPM_VERSION
build variable in the Cloudflare dashboard. It does not read packageManager, and pnpm
has no equivalent of .nvmrc, so that dashboard variable is your only lever. Node is
genuinely pinned everywhere, because Workers Builds does read .nvmrc.