Cloudflare Turnstile (CAPTCHA) Configuration
Turnstile protects login, registration, forgot-password, reset-password, and contact form from bots. Configure it in your .env with keys from the Cloudflare dashboard.
Step 1: Create a Turnstile site in Cloudflare
- Log in to Cloudflare Dashboard.
- Select your account (or create one).
- In the left sidebar, open Turnstile (under "Web3" or search for "Turnstile").
- Click Add site.
- Set:
- Site name — e.g. "Bellamy Book"
- Domain — Add your frontend/admin/api domains (e.g.
app.yourdomain.com,www.yourdomain.comif you use www redirect for SEO,admin.yourdomain.com,api.yourdomain.com). For local testing you can addlocalhost. - Widget type — "Managed" (recommended) or "Non-interactive" / "Invisible" if you prefer.
- Click Create and copy the Site Key and Secret Key.
Step 2: Set environment variables
In your .env:
| Variable | Description |
|---|---|
Turnstile__SiteKey | The Site Key from Cloudflare (public; used by the frontend). |
Turnstile__SecretKey | The Secret Key from Cloudflare (private; used by the API only). |
Turnstile__VerificationUrl | Leave as https://challenges.cloudflare.com/turnstile/v0/siteverify unless Cloudflare changes it. |
Turnstile__AllowedDomains__0, __1, … | Hostnames where the widget is allowed. Usually match your Traefik hosts: ${TRAEFIK_FRONTEND_HOST}, ${TRAEFIK_ADMIN_HOST}, ${TRAEFIK_API_HOST}, ${TRAEFIK_DOCS_HOST}. If you use www redirect for SEO, add ${TRAEFIK_FRONTEND_WWW_HOST}. |
Example:
Turnstile__SiteKey=0x4AAAAAAA...
Turnstile__SecretKey=0x4AAAAAAA...
Turnstile__VerificationUrl=https://challenges.cloudflare.com/turnstile/v0/siteverify
Turnstile__AllowedDomains__0=app.yourdomain.com
Turnstile__AllowedDomains__1=www.yourdomain.com
Turnstile__AllowedDomains__2=admin.yourdomain.com
Turnstile__AllowedDomains__3=api.yourdomain.com
Turnstile__AllowedDomains__4=docs.bellamybook.com
The backend already enables Turnstile for these endpoints: login, register, forgot-password, reset-password, contact. You can override with Turnstile__EnabledEndpoints__0, etc. if needed.
Step 3: Frontend and Admin (runtime – pre-built images)
With pre-built images, the frontend and admin get the Turnstile site key (and optional theme/size/endpoints) from runtime config — set these in your .env:
| Variable | Description |
|---|---|
VITE_TURNSTILE_SITE_KEY | Same value as Turnstile__SiteKey (public site key). |
VITE_TURNSTILE_THEME | Optional: light or dark. |
VITE_TURNSTILE_SIZE | Optional: normal or compact. |
VITE_TURNSTILE_ENABLED_ENDPOINTS | Optional: e.g. login,register,forgot-password,reset-password,contact. |
The backend uses Turnstile__SiteKey and Turnstile__SecretKey; the frontend and admin use VITE_TURNSTILE_SITE_KEY. Set both so API and UI use the same site key.
Step 4: Restart
After changing .env:
docker compose restart api frontend admin
Troubleshooting
Production (e.g. local server + Cloudflare Tunnel) and self-hosted (e.g. Google Cloud VPS + Cloudflare Tunnel) both use Cloudflare Tunnel; only the origin server differs. If production works but a new self-hosted domain (e.g. bellbooktest.online) does not:
- Turnstile 110200 — In Cloudflare Dashboard → Turnstile → your site → Domains, add every hostname for the new site (e.g.
admin.bellbooktest.online,app.bellbooktest.online). Production hostnames are usually already there; the new domain is not, so add it. - CSP blocking scripts — Rebuild and redeploy the admin (and frontend) image from this repo so the relaxed CSP (allowing
static.cloudflareinsights.comandcloudflareinsights.com) is in the build. Production may be running a build that already has it.
Disabling Turnstile
To disable Turnstile (e.g. for local dev), you can leave Turnstile__SecretKey and Turnstile__SiteKey empty or set a backend option to skip verification (if your app supports it). Check the API configuration for a "Turnstile disabled" or "skip verification" option.