---
title: Self-host on Cloudflare
description: Deploy Seal on your own Cloudflare account with D1, R2, and workers.dev
---

# Self-host on Cloudflare

Run Seal on **your** Cloudflare account. Signing loop works with Workers + D1 + R2.
No Vortex resource IDs. No Docker compose.

## Deploy to Cloudflare

[![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/VortexNYC/seal)

Seal is a pnpm monorepo. On the Workers Builds setup screen:

| Field | Value |
| ----- | ----- |
| Root directory | `/` |
| Build command | `pnpm install` |
| Deploy command | `pnpm selfhost` |

## CLI (recommended)

Requires Node 24+, pnpm 12+, and a Cloudflare account.

```bash
git clone https://github.com/VortexNYC/seal.git
cd seal
pnpm install
pnpm exec wrangler login
pnpm selfhost
```

Non-interactive secrets:

```bash
BETTER_AUTH_SECRET=$(openssl rand -base64 32) \
TOKEN_HASH_SECRET=$(openssl rand -base64 32) \
INTERNAL_API_KEY=$(openssl rand -base64 32) \
pnpm selfhost
```

What the script does:

1. Prompts for (or reads) the three secrets above
2. Deploys `apps/api` `[env.selfhost]` — wrangler **auto-provisions** D1 + R2
3. Applies D1 migrations by binding name (`D1`)
4. Sets `BETTER_AUTH_URL` / `APP_URL` / `ALLOWED_ORIGINS` to your `*.workers.dev` URLs
5. Builds and deploys `apps/web` against that API

You get something like:

```text
Web:  https://seal-web.<account>.workers.dev
API:  https://seal-api.<account>.workers.dev
```

## Cold-account checklist (stranger-shaped)

Treat a **new Cloudflare login** as the real proof. Vortex-account dogfood is
safe (`seal-selfhost-*` names) but does not prove stranger readiness.

Do these in order; stop when one fails.

1. **Clone + install on a clean machine** (no prior `wrangler` login, no
   copied Vortex `.dev.vars`):
   ```bash
   git clone https://github.com/VortexNYC/seal.git && cd seal
   pnpm install
   pnpm exec wrangler login   # *your* account
   pnpm selfhost
   ```
2. **Open the printed web URL** → sign up → verify email → create a workspace
3. **Developer → API Keys** → create `seal_…`
4. **Golden path** (CLI + public signing half → completed → audit):
   ```bash
   SEAL_API_KEY=seal_… pnpm run prove:golden-path \
     --api https://seal-selfhost-api.<account>.workers.dev \
     --app https://seal-selfhost-web.<account>.workers.dev
   ```
   Alternate raw HTTP smoke (no CLI):
   ```bash
   SEAL_API_KEY=seal_… node scripts/smoke-prod.mjs \
     --api https://seal-selfhost-api.<account>.workers.dev
   ```
5. **Sign once in a real browser** from the `signing_url` (or the UI) so you
   trust the SPA route, not only the public API harness
6. **Email** — only after the loop above is green (see next section)

If step 1–4 fail on a cold account, that is a product bug — fix docs/script
before claiming self-host works.

## Email (required for real invites)

`selfhost` ships with a Cloudflare Email Sending binding (`EMAIL`). Signing
links still work from the UI/API without mail; invites need a verified sender
domain on **your** Cloudflare account.

1. Enable sending for your domain:
   `pnpm exec wrangler email sending enable your-domain.com`
2. Set `allowed_sender_addresses` / `EMAIL_FROM` under `[env.selfhost]` in
   `apps/api/wrangler.toml` to an address on that domain
3. Redeploy: `pnpm --filter @seal/api exec wrangler deploy --env selfhost`

Vortex dogfood uses `noreply@seal.nyc` (domain already enabled on the account).

## Optional workers

| Worker | Needed for |
| ------ | ---------- |
| API + web | Core upload → send → sign |
| anydoc | Rich PDF field detection |
| convert | DOCX → PDF (Containers) |
| mcp | Agent MCP surface on your origin |

PDF upload and signature collection work without anydoc/convert.

## Custom domains

Start on `*.workers.dev`. When you own DNS, add routes under `[env.selfhost]`
(or promote to a dedicated `production` env with your own D1/R2 IDs — never
reuse Vortex’s).

## Local development

For laptop-only work (no deploy):

```bash
cp apps/api/.dev.vars.example apps/api/.dev.vars
cp apps/web/.env.example apps/web/.env.local
pnpm run dev
```

See the repo [CONTRIBUTING.md](https://github.com/VortexNYC/seal/blob/main/CONTRIBUTING.md).

## Safety

Selfhost worker / D1 / R2 names are `seal-selfhost-*` so they cannot overwrite
hosted production (`seal-api`, `seal-web`, `seal-global`, `seal-documents`). You
can dogfood on the Vortex account; for a customer-shaped cold proof, use a
separate Cloudflare login.
