Last updated Oct 27, 2025

Self-host on Cloudflare

Deploy Fumi to your own Cloudflare account with Workers, D1, and KV.

Requirements

  • Cloudflare account with Workers, D1, and KV enabled.
  • Wrangler CLI authenticated locally (bunx wrangler login).
  • Bun runtime 1.1.0 or newer.

Baseline deployment

Work through the Quickstart checklist to clone the repository, install dependencies, and run bun run setup. The guided script authenticates Wrangler, provisions D1, applies migrations, and deploys your Worker.

Configure runtime variables

Use bunx wrangler secret put for any additional secrets (OAuth client IDs, webhook signing keys). Keep environment-specific values in distinct Workers or use environment bindings.

Recommended secrets:

  • JWT_SECRET (generated automatically, re-run the setup script to rotate).
  • INCOMING_WEBHOOK_SECRET for signed action calls.
  • PAGERDUTY_ROUTING_KEY or similar third-party credentials.

Verify the deployment

  • Open the dashboard URL printed after deployment.
  • Use bunx wrangler tail to confirm events reach the ingestion Worker.
  • Run bun test locally to ensure SDK contract tests pass in your environment.

Rotate ingestion keys

Ship a new API key any time a credential leaks or you’re doing scheduled maintenance. The rotation flow keeps old keys temporarily valid (rotating) so clients have time to switch, then disables them once traffic stops.

Key scopes & expirations

Every entry in project_api_keys now carries a permissions value and an expires_at timestamp. Use these to lock credentials to exactly what a client needs:

  • ingest keys can only hit POST /api/v1/events and POST /api/v1/contexts.
  • manage keys are reserved for forthcoming read/export endpoints and higher-risk automations—store them in your vault and audit their usage closely.

Keys auto-disable once expires_at passes. The dashboard modal and bun run rotate-keys both default to 90 days, but you can choose any value between 1 and 365 days—or opt out entirely by leaving expires_at null if you really need a never-expiring credential. Prefer shorter TTLs for day-to-day ingestion and reserve never-expiring keys for emergency break-glass scenarios.

bun run rotate-keys

The helper script walks through:

  1. Selecting the project + D1 database.
  2. Minting a new fumi_xxx key with the permission scope (ingest vs. manage) and TTL you choose, inserting it into project_api_keys, and pointing the project at it.
  3. Optionally syncing the new value into a Cloudflare secret binding (so dependent Workers/CI pull it automatically).
  4. Running bun run deploy to redeploy your Worker.
  5. Sending a rotation.verify event through /api/v1/events to confirm ingest succeeds.
  6. Showing the list of rotating keys so you can disable the stale one once traffic drains.

You’ll be asked for the Worker base URL (used for verification) and which secret binding to refresh. Provide LATEST_API_KEY or any binding you rely on; press enter to skip.

Manual fallback

If you prefer raw commands:

  1. Mark the current key rotating and insert a replacement inside D1:
    UPDATE project_api_keys SET status = 'rotating' WHERE project_id = '<project-id>' AND status = 'active';
    INSERT INTO project_api_keys (id, project_id, api_key, status, permissions, created_at, expires_at)
    VALUES (
      '<new-id>',
      '<project-id>',
      'fumi_<uuid>',
      'active',
      'ingest', -- or 'manage' for full read/manage access
      strftime('%s','now'),
      NULL -- or strftime('%s','now') + (90 * 24 * 60 * 60) for auto-expiry
    );
    UPDATE projects SET api_key = 'fumi_<uuid>' WHERE id = '<project-id>';
  2. Update any secret stores (.env files, GitHub Actions, Workers secrets) with the new value.
  3. Redeploy the Worker.
  4. POST a test event to /api/v1/events using the new key.
  5. Once clients finish switching, UPDATE project_api_keys SET status = 'disabled', disabled_at = strftime('%s','now') WHERE id = '<old-key-id>';.

Optional optimizations

  • Enable Workers KV caching for read-heavy endpoints.
  • Attach Durable Objects if you need bidirectional WebSocket fan-out.
  • Configure Cloudflare Alerts for Worker exceptions.

Troubleshooting

  • D1 migrations failing: ensure your account has D1 in the target region; rerun bunx wrangler d1 migrations apply <db> with --remote.
  • Secrets missing: rerun the setup script; it safely re-creates values if required.
  • Edge deployment slow: confirm you are using the global Worker URL instead of a proxied domain.