Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 135 additions & 0 deletions src/content/docs/en/self-hosting/cloudflare.mdx
Original file line number Diff line number Diff line change
@@ -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.<subdomain>.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 <token>` — copy the token. You don't need to add a public hostname.

## 3. Generate your environment file

Your Worker will be at `procaptcha.<subdomain>.workers.dev`. Your `<subdomain>` is shown in the dashboard under
**Workers & Pages**.

```bash
pnpm exec tsx ./dev/scripts/src/scripts/generateSelfHostedEnv.ts \
--host procaptcha.<subdomain>.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 <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.<subdomain>.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.<subdomain>.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.<subdomain>.workers.dev/?sitekey=<your site key>`. 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
<script type="module" src="https://procaptcha.<subdomain>.workers.dev/procaptcha.bundle.js" async defer></script>
<div class="procaptcha" data-sitekey="5Fpd..."></div>
```

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.
20 changes: 13 additions & 7 deletions src/content/docs/en/self-hosting/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ that is what the managed tiers are for.

## Before you start

<aside>
No server with a public IP? [Run it on Cloudflare's free plan](/en/self-hosting/cloudflare/) instead — any machine with
Docker will do, and you don't need a domain.
</aside>

- A host with Docker and Docker Compose, and at least 4&nbsp;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.
Expand All @@ -46,14 +51,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
```
Expand Down Expand Up @@ -85,10 +92,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
```
Expand Down Expand Up @@ -156,8 +164,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
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/en/nav.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
Loading