From c58bd10e88531ae6dc533a66a66637c1274bbdf7 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Thu, 22 Oct 2026 13:27:35 +0100 Subject: [PATCH 1/2] docs(self-hosting): make the guide work from a fresh clone Clone with submodules, build before running the env script, and drop -f from docker compose now that .env sets COMPOSE_FILE (prosopo/captcha#3587). Co-Authored-By: Claude Opus 5.5 --- src/content/docs/en/self-hosting/index.mdx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/content/docs/en/self-hosting/index.mdx b/src/content/docs/en/self-hosting/index.mdx index 723970511e3a0..dfcae62b91a64 100644 --- a/src/content/docs/en/self-hosting/index.mdx +++ b/src/content/docs/en/self-hosting/index.mdx @@ -46,14 +46,16 @@ entirely by environment variables — the compose file below wires it up. ## 1. Clone and generate your environment file -One script writes the whole thing: it generates the provider account, the site key account and strong random passwords -for Mongo and Redis, and fills in every other value from the hostname you give it. +Clone with submodules, install, and build the workspace once — the env script and every later step use the built +packages. Then one script writes the whole environment file: it generates the provider account, the site key account and +strong random passwords for Mongo and Redis, and fills in every other value from the hostname you give it. ```bash -git clone https://github.com/prosopo/captcha +git clone --recurse-submodules https://github.com/prosopo/captcha cd captcha corepack enable pnpm install --frozen-lockfile +pnpm run build:all pnpm exec tsx ./dev/scripts/src/scripts/generateSelfHostedEnv.ts --host captcha.example.com ``` @@ -85,10 +87,11 @@ Never reuse the addresses above. They are from a throwaway run and are public by ## 2. Start the stack -The compose file ships with the repo, so there is nothing to paste: +The compose file, `docker-compose.self-hosted.yml`, ships with the repo. Your `.env` sets `COMPOSE_FILE` to it, so this +and every later `docker compose` command finds it without `-f`. Run them from the repository root. ```bash -docker compose -f docker-compose.self-hosted.yml up -d +docker compose up -d curl localhost:9229/healthz # the provider directly curl https://captcha.example.com/healthz # through Caddy, once the certificate is issued ``` @@ -156,8 +159,6 @@ The published bundle on Prosopo's CDN is compiled to talk to Prosopo's fleet, so `PROSOPO_PROVIDER_LIST` baked in. This is a plain production build — you are not editing any source: ```bash -pnpm run build:all - NODE_ENV=production \ PROSOPO_PROVIDER_LIST=https://captcha.example.com \ pnpm --filter @prosopo/procaptcha-bundle run bundle From bc014f6237e977b319e809a22c4ae3fe9f02ef03 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Thu, 22 Oct 2026 15:06:17 +0100 Subject: [PATCH 2/2] docs(self-hosting): add Self-Hosting on Cloudflare's Free Plan A provider on any machine with Docker, fronted by a Worker on workers.dev that forwards to it through a Cloudflare Tunnel. Matches prosopo/captcha's new docker/cloudflare template and generateSelfHostedEnv.ts --cloudflare. Co-Authored-By: Claude Opus 5.5 --- .../docs/en/self-hosting/cloudflare.mdx | 135 ++++++++++++++++++ src/content/docs/en/self-hosting/index.mdx | 5 + src/i18n/en/nav.ts | 5 + 3 files changed, 145 insertions(+) create mode 100644 src/content/docs/en/self-hosting/cloudflare.mdx diff --git a/src/content/docs/en/self-hosting/cloudflare.mdx b/src/content/docs/en/self-hosting/cloudflare.mdx new file mode 100644 index 0000000000000..30e19bd853856 --- /dev/null +++ b/src/content/docs/en/self-hosting/cloudflare.mdx @@ -0,0 +1,135 @@ +--- +title: Self-Hosting on Cloudflare's Free Plan +description: Run your own Procaptcha provider on any machine with Docker, with Cloudflare in front of it. No domain, no public IP, no open ports and no certificates. +i18nReady: true +--- + +The [main guide](/en/self-hosting/) assumes a server with a public IP and ports 80 and 443 open. If you don't have one, +any machine that can run Docker will do — a spare PC, a home server, a laptop — with Cloudflare in front of it. + +All you need is a **free Cloudflare account**. No domain, no public IP, no open ports, no certificates. + +## How it works + +One Worker on your free `workers.dev` subdomain does two jobs: + +- it serves the widget, and a demo page for testing; +- it forwards API calls to your provider through a [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/), + using a [Workers VPC](https://developers.cloudflare.com/workers-vpc/) binding. + +The tunnel connects **outbound** from your machine, so nothing on your network is exposed. Cloudflare handles HTTPS, +so the Caddy container from the main guide is not used. + +| Part | Where it runs | +| --- | --- | +| Mongo, Redis, the provider, `cloudflared` | Docker on your machine | +| The widget, the demo page, the API forwarder | A Worker on `procaptcha..workers.dev` | + +## 1. Clone and build + +Follow [step 1 of the main guide](/en/self-hosting/#1-clone-and-generate-your-environment-file) up to and including +`pnpm run build:all`, but don't generate the environment file yet — you'll generate it below with the Worker's +hostname. + +## 2. Create the tunnel + +From the repository root: + +```bash +npx wrangler@4.151.3 login +npx wrangler@4.151.3 tunnel create procaptcha # note the ID it prints +``` + +Open the tunnel in the Cloudflare dashboard: **Zero Trust → Networks → Tunnels → procaptcha**. Its install command +contains `--token ` — copy the token. You don't need to add a public hostname. + +## 3. Generate your environment file + +Your Worker will be at `procaptcha..workers.dev`. Your `` is shown in the dashboard under +**Workers & Pages**. + +```bash +pnpm exec tsx ./dev/scripts/src/scripts/generateSelfHostedEnv.ts \ + --host procaptcha..workers.dev --cloudflare +``` + +Paste the token into `CLOUDFLARE_TUNNEL_TOKEN` in `.env`. + +`--cloudflare` sets `COMPOSE_FILE` to load +[`docker-compose.self-hosted.cloudflare.yml`](https://github.com/prosopo/captcha/blob/main/docker-compose.self-hosted.cloudflare.yml) on top of the main compose +file. It adds `cloudflared` and leaves Caddy out. + +## 4. Start the stack + +```bash +docker compose up -d +``` + +Four containers start: Mongo, Redis, the provider and `cloudflared`. The tunnel shows as **Healthy** in the +dashboard once `cloudflared` connects. + +## 5. Let Workers reach the provider + +```bash +npx wrangler@4.151.3 vpc service create procaptcha-provider \ + --type http --tunnel-id --hostname provider --http-port 9229 +``` + +Put the service ID it prints into [`docker/cloudflare/wrangler.jsonc`](https://github.com/prosopo/captcha/blob/main/docker/cloudflare/wrangler.jsonc), in place +of `REPLACE_WITH_YOUR_VPC_SERVICE_ID`. + +`provider` is the provider's service name in the compose file. `cloudflared` resolves it on the Docker network, so +it keeps working when the container restarts and gets a new address. + +## 6. Register your site key + +As in [step 3 of the main guide](/en/self-hosting/#3-register-your-site-key), with the Worker's hostname added to +`--domains` so its demo page works: + +```bash +docker compose exec provider /nodejs/bin/node /usr/src/app/provider.cli.bundle.js site_key_register \ + "$(grep '^PROSOPO_SITE_KEY=' .env | cut -d= -f2)" enterprise \ + --captcha_type puzzle \ + --domains example.com procaptcha..workers.dev \ + --pow_difficulty 4 \ + --frictionless_threshold 0.5 \ + --image_threshold 0.8 +``` + +## 7. Build the widget and deploy the Worker + +```bash +NODE_ENV=production \ +PROSOPO_PROVIDER_LIST=https://procaptcha..workers.dev \ +pnpm --filter @prosopo/procaptcha-bundle run bundle + +npx wrangler@4.151.3 deploy --config docker/cloudflare/wrangler.jsonc +``` + +## 8. Try it + +Open `https://procaptcha..workers.dev/?sitekey=`. The demo page shows whether the provider +is reachable, renders the CAPTCHA, and shows the response token once it is solved. + +On your own site, load the widget from the Worker: + +```html + +
+``` + +Verify tokens as in [step 5 of the main guide](/en/self-hosting/#5-verify-tokens-on-your-server). +`PROSOPO_PROVIDER_LIST` in your `.env` already points at the Worker. + +Everything else in the main guide applies unchanged, including +[bringing your own detector](/en/self-hosting/#bring-your-own-detector) and [image CAPTCHAs](/en/self-hosting/#image-captchas). + +## Worth knowing + +- **Workers VPC is in open beta.** It is what lets the Worker reach the tunnel. +- **The free Workers plan has a daily request allowance.** Each solve is a handful of requests, so check + [the current limits](https://developers.cloudflare.com/workers/platform/limits/) against your traffic. +- **Visitor addresses are passed through.** The Worker sets `X-Forwarded-For` to each visitor's address. Without that, + the provider would see every visitor as the tunnel, and per-IP rate limits and rules would apply to all of them + together. +- **The provider only runs while your machine does.** The containers restart on their own when Docker comes back. diff --git a/src/content/docs/en/self-hosting/index.mdx b/src/content/docs/en/self-hosting/index.mdx index dfcae62b91a64..1f94177d8eeb3 100644 --- a/src/content/docs/en/self-hosting/index.mdx +++ b/src/content/docs/en/self-hosting/index.mdx @@ -35,6 +35,11 @@ that is what the managed tiers are for. ## Before you start + + - A host with Docker and Docker Compose, and at least 4 GB of RAM. - A public domain name pointing at that host, with ports 80 and 443 reachable from the internet. The provider itself speaks plain HTTP on 9229 and obtains no certificates; the bundled Caddy container does that for it. diff --git a/src/i18n/en/nav.ts b/src/i18n/en/nav.ts index 691b22cbf9578..71e214cdd096e 100644 --- a/src/i18n/en/nav.ts +++ b/src/i18n/en/nav.ts @@ -138,6 +138,11 @@ export default [ slug: 'self-hosting/', key: 'self-hosting/', }, + { + text: 'Run It on Cloudflare (Free)', + slug: 'self-hosting/cloudflare/', + key: 'self-hosting/cloudflare/', + }, {text: 'Prosopo Protect (Edge)', header: true, type: 'learn', key: 'protect-edge'}, { text: 'Overview',