Cloudflare
The container behind a Cloudflare Tunnel, the public knowledge base on its own hostname, or the whole app as a Worker.
Two ways, and they are not the same thing:
- The container behind a Cloudflare Tunnel. Trove KB keeps running in Docker on your own host; Cloudflare publishes it with no port open to the internet. Start here.
- A Cloudflare Worker. No server at all, but the database moves to Hyperdrive, attachments to R2, and the vault sidecar needs a tunnel of its own.
Both run the same code against the same schema, and password hashes are interchangeable between them.
Behind a Cloudflare Tunnel
browser -> Cloudflare edge -> tunnel -> cloudflared (this host) -> 127.0.0.1:3080 -> container
-
Point
APP_URLat the hostname people will type, for examplehttps://trove-kb.yourdomain.com. Session cookies areSecureas soon as it ishttps, the OAuth state check is built from it, and every absolute link comes from it. A mismatch shows up as a sign-in page that loops, or asstate_mismatchon single sign-on. -
Publish the hostname. Zero Trust → Networks → Tunnels → your tunnel → Public Hostname → Add: subdomain
trove-kb, type HTTP, URL127.0.0.1:3080, HTTP Host Header left empty. Or in/etc/cloudflared/config.yml:ingress: - hostname: trove-kb.yourdomain.com service: http://127.0.0.1:3080 - service: http_status:404 -
Close the port to everything else:
APP_BIND=127.0.0.1, thendocker compose up -d. -
Check it end to end from somewhere that is not this host:
curl https://trove-kb.yourdomain.com/api/healthanswers{"status":"ok"}.
Uploads pass through Cloudflare, which caps a request body at 100 MB on free and Pro plans; keep MAX_UPLOAD_MB under it. Cloudflare Access can sit in front for a second gate; leave /api/* out of the policy or scope it to a service token, since an API or MCP client has nowhere to put an Access login page. Single sign-on redirect URIs move with the hostname: https://trove-kb.yourdomain.com/api/auth/callback/oidc.
Publishing the public knowledge base
The public knowledge base (/pub/kb) belongs on a hostname of its own, such as kb.yourdomain.com, behind a proxy that passes the knowledge base and nothing else:
server {
listen 80;
server_name kb.yourdomain.com;
proxy_set_header X-Real-IP $http_cf_connecting_ip;
proxy_set_header X-Forwarded-For $http_cf_connecting_ip;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Cookie "";
proxy_hide_header Set-Cookie;
location = / { return 302 https://$host/pub/kb; }
location ^~ /pub/kb { proxy_pass http://127.0.0.1:3080; }
location ^~ /_next/static/ { proxy_pass http://127.0.0.1:3080; }
location = /api/branding/logo { proxy_pass http://127.0.0.1:3080; }
location = /favicon.ico { proxy_pass http://127.0.0.1:3080; }
location / { return 404; }
}
Publish kb.yourdomain.com on the tunnel to http://localhost:80. In Trove KB, under Admin → Settings → Public knowledge base, choose who is admitted; in Zero Trust, add a self-hosted Access application for the hostname with a Bypass policy for the same address ranges. Each collection has Show on the public site, off by default.
Running as a Worker
| Piece | Why |
|---|---|
| A Postgres database | Neon, Supabase, RDS, anything Hyperdrive can reach |
| Hyperdrive | Pools connections at the edge; a Worker cannot hold one open |
| An R2 bucket | Attachments. Workers have no filesystem |
| Workers paid plan | Argon2id costs a few hundred milliseconds of CPU per sign-in |
npx wrangler hyperdrive create trove-kb --connection-string="postgres://…"
npx wrangler r2 bucket create trove-kb-uploads
# put the Hyperdrive id into wrangler.jsonc
npx wrangler secret put AUTH_SECRET
npx wrangler secret put APP_URL
npx wrangler secret put CRON_SECRET
DATABASE_URL="postgres://…" npm run db:migrate
DATABASE_URL="postgres://…" npm run db:seed # optional
npm run cf:deploy
What differs from the container: webhook retries and schedule passes come from a Cron Trigger calling /api/internal/webhooks with CRON_SECRET; attachments go to R2 through the TROVE_UPLOADS binding with STORAGE_DRIVER=r2; each request gets its own database connection, which Hyperdrive keeps warm.
Secrets in a cloud deployment
The vault sidecar cannot run on Workers. Either stay on link mode, or keep bw-serve on your own network and reach it through a tunnel that only Cloudflare Access can traverse, with the Worker presenting a service token:
npx wrangler secret put BW_SERVE_URL # https://vault-bridge.yourdomain.com
npx wrangler secret put BW_SERVE_ACCESS_CLIENT_ID
npx wrangler secret put BW_SERVE_ACCESS_CLIENT_SECRET
If Access refuses the token, or the sidecar is down, secret fields degrade to link mode and say so.