Skip to content

Deploy to Cloudflare

Spantail is designed to be deployed to your own Cloudflare account. The recommended path is a fork connected to Cloudflare Workers Builds: after a one-time setup, every push — and GitHub’s Sync fork button — deploys automatically, running database migrations first. You never commit per-instance IDs, so staying up to date with upstream never conflicts.

If you would rather drive the deploy from your own machine, a manual Wrangler flow is kept below.

To run Spantail on your machine before deploying — local D1 emulation, no Cloudflare resources:

git clone https://github.com/spantail/spantail.git
cd spantail
pnpm install

# create local env vars, then set BETTER_AUTH_SECRET (>= 32 chars)
cp apps/web/.dev.vars.example apps/web/.dev.vars

pnpm db:migrate:local   # apply migrations to the local D1 emulator
pnpm dev                # SPA + Worker on http://localhost:5173

See Configuration for every value in .dev.vars. The recommended deploy below needs no local Node, pnpm, or Wrangler to deploy — only a GitHub account and a Cloudflare account (a database backup during an upgrade does use local Wrangler).

  1. Fork the repository on GitHub (Fork → your account or an organization). A fork of a public repository is itself public — which is fine here, because you commit no secrets or per-instance IDs (they live in Cloudflare). If you require a private repository, push a private mirror instead and upgrade by merging from upstream; the Sync fork button only works on a real fork, though Workers Builds itself supports private repositories.

  2. Create the D1 database and R2 bucket in your Cloudflare account. You can use the dashboard (Storage & Databases → D1 and R2) or Wrangler:

    wrangler d1 create spantail-db
    wrangler r2 bucket create spantail-uploads

    Copy the D1 database_id from the output — it is the one per-instance value you supply below.

  3. Create the Worker from your fork. In the dashboard, go to Workers & Pages → Create → Connect GitHub, install the Cloudflare GitHub app for your fork, and select it. Then set:

    • Project name: spantail — match name in apps/web/wrangler.jsonc. If they differ, Cloudflare opens a pull request against your fork to reconcile the name, which would conflict with Sync fork.
    • Build command: clear it if a value is pre-filled — it must be empty.
    • Deploy command: replace the pre-filled default with pnpm run deploy:ci.
    • Advanced settings → Path: apps/web
    • Advanced settings → Add variable: D1_DATABASE_ID = the database_id from step 2

    deploy:ci builds, applies migrations to your remote database, and deploys — all in one command. The D1_DATABASE_ID build variable is injected into the build, so nothing per-instance is ever committed to your fork.

  4. Set the session-signing secret. Add a Worker secret named BETTER_AUTH_SECRET (a value of at least 32 characters) — in the Worker’s Settings → Variables and Secrets, or with Wrangler once the Worker exists:

    wrangler secret put BETTER_AUTH_SECRET --name spantail   # paste e.g. `openssl rand -base64 32`
  5. Deploy. Save the setup to trigger the first build; it applies all migrations and deploys the Worker to its *.workers.dev URL. BETTER_AUTH_URL can stay unset — Spantail derives its origin from the request (see Configuration to pin a custom domain).

On your fork’s page, click Sync fork to pull the latest release from upstream. Workers Builds picks up the push and runs deploy:ci — migrations then deploy — automatically. Because your fork carries no committed per-instance IDs, Sync fork never conflicts.

Before a major upgrade, back up your database (wrangler d1 export spantail-db --remote); Cloudflare D1 Time Travel also lets you restore to a point in time.

If you prefer to deploy from your own machine, provision the resources and deploy directly. This needs the local requirements (Node, pnpm, Wrangler).

  1. Clone and install.

    git clone https://github.com/spantail/spantail.git
    cd spantail
    pnpm install
  2. Create the resources and copy the generated D1 ID into apps/web/wrangler.jsonc.

    wrangler d1 create spantail-db
    wrangler r2 bucket create spantail-uploads

    Set the returned database_id in the d1_databases block of apps/web/wrangler.jsonc (it ships with a placeholder ID).

  3. Set the session-signing secret. Generate a value of at least 32 characters.

    pnpm --filter web exec wrangler secret put BETTER_AUTH_SECRET   # paste e.g. `openssl rand -base64 32`
  4. Apply migrations, then deploy. Order matters — migrate before deploy.

    pnpm db:migrate:remote
    pnpm run deploy

    Use pnpm run deploy (not pnpm deploy, which is a built-in pnpm command).

By default the app derives its origin from each request, so links and OAuth callbacks work on the *.workers.dev URL with no extra configuration. To pin a canonical origin — a custom domain, or a proxy that rewrites the host — set BETTER_AUTH_URL. This and the optional social-login and email secrets are covered on the Configuration page.

If a proxy in front of the instance applies path-scoped rules — for example a Cloudflare Access policy that bypasses SSO for /api and /mcp — make those path matchers tolerate a doubled slash (//api/…), or match on a prefix that covers it. A client configured with a trailing-slash base URL can emit such a path (the plugin’s MCP endpoint URL is one it cannot normalize itself), and the Worker collapses doubled slashes only once the request reaches it.

The deployed instance loaded at its Workers URL, showing the sign-up
screen.

With the Worker live, the first person to sign up becomes the instance administrator. Continue to the Initial setup wizard.