Self-hosted Cap
Cap is the replacement candidate for mCaptcha. It keeps the same HiveGrid
principle as mCaptcha: self-hosted, no Google/Cloudflare CAPTCHA dependency,
and no third-party widget CDN. The app loads /assets/widget.js and WASM from
the Cap instance itself.
Domain requirement
Cap needs a public HTTPS URL. The browser calls the Cap instance to generate and
solve challenges, and the app server calls the same instance for siteverify.
A raw private IP is not a good production setup because browser TLS, CORS,
Cloudflare proxying, and asset loading all depend on a stable public origin.
Recommended hostnames:
- Staging:
https://captcha-staging.hivegrid.deon node1 (176.9.111.179) - Production:
https://captcha.hivegrid.deonhivegrid-prod-worker
Staging on node1
node1 runs Cap as a plain docker-compose stack and exposes it through the
existing Coolify Traefik proxy (coolify-proxy). The compose service also
joins the external coolify network with the stable alias cap-staging, so
Traefik can reach it without publishing Cap directly to the public interface.
-
Create DNS
captcha-staging.hivegrid.de -> 176.9.111.179, DNS-only until Traefik has issued the certificate. -
Copy
docker-compose.node1.ymland.env.exampleto/opt/cap/on node1. -
Create
/opt/cap/.envfrom.env.example, setADMIN_KEY, and set:CORS_ORIGIN=https://staging-app.hivegrid.de,https://staging-api.hivegrid.de,https://staging.hivegrid.de,https://staging-portal.hivegrid.de REDIS_PREFIX=The supplied stack owns its Valkey instance exclusively. Keep
REDIS_PREFIXempty with Cap 3.1.5: its site-key creation path does not prefixHMSET, while the challenge path prefixes reads. A non-empty value therefore creates keys that are visible in the dashboard but always return404from/<sitekey>/challenge. -
Start Cap:
docker compose -f docker-compose.node1.yml up -d -
Install the Traefik dynamic config on node1, then restart the proxy:
cp traefik-staging.yaml /data/coolify/proxy/dynamic/cap-staging.yaml docker restart coolify-proxy -
Open
https://captcha-staging.hivegrid.de, log in withADMIN_KEY, and create site keys forlogin,forms, andbooking. Keep each public sitekey and its matching site secret. Before rolling them out, prove thatPOST /<sitekey>/challengereturns200for every key and includes the requesting staging origin inAccess-Control-Allow-Origin. -
Set the staging web app env:
CAPTCHA_PROVIDER=cap CAP_INSTANCE_URL=https://captcha-staging.hivegrid.de CAP_SITEKEY_LOGIN=<login sitekey> CAP_SITE_SECRET_LOGIN=<login secret> CAP_SITEKEY_FORMS=<forms sitekey> CAP_SITE_SECRET_FORMS=<forms secret> CAP_SITEKEY_BOOKING=<booking sitekey> CAP_SITE_SECRET_BOOKING=<booking secret> CAPTCHA_FAIL_OPEN=true
Production on worker/services VM
Production should run as a Coolify compose service on hivegrid-prod-worker
(49.13.24.190, private 10.0.0.5) with FQDN captcha.hivegrid.de.
-
In Coolify, create a Docker Compose service from
infra/cap/docker-compose.coolify.yml. -
Set
SERVICE_FQDN_CAP_3000=captcha.hivegrid.de. -
Create the service
.envfrom.env.exampleand set:CORS_ORIGIN=https://app.hivegrid.de,https://admin.hivegrid.de,https://api.hivegrid.de -
Deploy and verify
https://captcha.hivegrid.dereturns the Cap dashboard. -
Create the same three site keys (
login,forms,booking). -
Set the production web env to the
CAP_*values and redeploy web. -
Keep the old
MCAPTCHA_*values available until the Cap rollout is verified. Rollback isCAPTCHA_PROVIDER=mcaptchaplus a web redeploy.
App integration contract
The web app reads:
CAPTCHA_PROVIDER=cap
CAP_INSTANCE_URL=https://...
CAP_SITEKEY_LOGIN=...
CAP_SITE_SECRET_LOGIN=...
CAP_SITEKEY_FORMS=...
CAP_SITE_SECRET_FORMS=...
CAP_SITEKEY_BOOKING=...
CAP_SITE_SECRET_BOOKING=...
CAPTCHA_FAIL_OPEN=true
When Cap is active, the UI renders:
<cap-widget data-cap-api-endpoint="https://<cap-host>/<sitekey>/"></cap-widget>
The widget injects cap-token into the enclosing form. Server-side verification
uses:
POST https://<cap-host>/<sitekey>/siteverify
Content-Type: application/json
{ "secret": "<site secret>", "response": "<cap-token>" }
The expected valid response is { "success": true }.
Caddyfile.staging.snippet is kept as a fallback for a non-Coolify node1
setup. The active staging setup uses traefik-staging.yaml.
Cutover checklist
- Staging Cap dashboard reachable over HTTPS.
/assets/widget.jsand/assets/cap_wasm_bg.wasmreturn 200 from the Cap host.- Staging web env uses
CAPTCHA_PROVIDER=cap. - Register/login captcha path verifies with a real browser.
- Public form with captcha enabled submits successfully once and rejects token replay.
- Production Cap deploy is healthy behind Cloudflare.
- Production web deploy completed after
CAP_*env is set. - mCaptcha rollback env remains available until Cap has run through one live deploy cycle.