Cloudflare Workers Deployment
Deploy Inkweld to Cloudflare's global edge network for low-latency access worldwide. This guide walks you through the interactive setup wizard - the easiest way to get started.
Overview
Cloudflare deployment uses:
- Cloudflare Workers - Serverless backend API at the edge
- Cloudflare Pages - Global CDN for the Angular frontend
- D1 Database - SQLite-based database for user data and projects
- R2 Storage - Object storage for media files (optional)
- Durable Objects - Real-time collaborative editing with Yjs
Prerequisites
Before you begin:
- Cloudflare Account - Sign up free
- Git - For cloning the repository
- Bun 1.3+ - Backend runtime and package manager
:::tip Free Tier Available Cloudflare offers generous free tiers for all required services. You can run Inkweld without any charges for development and small-scale use. :::
Quick Start with Setup Wizard
The setup wizard automates the entire deployment process:
# Clone the repository
git clone https://github.com/bobbyquantum/inkweld.git
cd inkweld
# Install dependencies
bun install
# Run the Cloudflare setup wizard
npm run cloudflare:setup
What the Wizard Does
The wizard guides you through each step:
============================================================
Inkweld Cloudflare Setup
============================================================
✅ Wrangler CLI is installed
✅ Logged in to Cloudflare
============================================================
Environment Selection
============================================================
Available environments:
preview - Pre-production environment for testing
production - Live production environment
Set up PREVIEW environment? (y/n): y
Set up PRODUCTION environment? (y/n): y
Step 1: Worker Configuration
The wizard detects your Cloudflare account and suggests unique worker names:
============================================================
Worker Configuration
============================================================
ℹ️ Worker names must be globally unique across all Cloudflare accounts.
ℹ️ Detected account: your-account-name
Worker name for PREVIEW (default: your-account-inkweld-preview):
✅ Preview worker URL: https://your-account-inkweld-preview.workers.dev
Continue with this URL? (y/n): y
Worker name for PRODUCTION (default: your-account-inkweld):
✅ Production worker URL: https://your-account-inkweld.workers.dev
Continue with this URL? (y/n): y
:::info Worker Name Uniqueness Worker names must be globally unique across all Cloudflare accounts. The wizard suggests names based on your account to avoid conflicts. :::
Step 2: Import Existing Configuration
If you're setting up on a new machine with existing deployments, the wizard can import your configuration:
Try to import existing environment variables from Cloudflare? (y/n): y
✅ Found existing preview configuration
✅ Found existing production configuration
Step 3: Create Resources
The wizard creates all required Cloudflare resources:
============================================================
Creating D1 Databases
============================================================
ℹ️ Creating D1 database: inkweld_preview...
✅ Created database "inkweld_preview" with ID: abc123...
ℹ️ Creating D1 database: inkweld_prod...
✅ Created database "inkweld_prod" with ID: def456...
============================================================
Creating R2 Storage Buckets
============================================================
✅ Created R2 bucket: inkweld-storage-preview
✅ Created R2 bucket: inkweld-storage
============================================================
Creating Cloudflare Pages Projects
============================================================
✅ Created Pages project: inkweld-frontend-preview
✅ Created Pages project: inkweld-frontend
Step 4: Generate Frontend Configuration
The wizard automatically generates environment files for the frontend:
============================================================
Generating Frontend Environment Files
============================================================
ℹ️ Frontend environment files configure the API URLs for each environment.
ℹ️ Workers will be available at:
ℹ️ Preview: https://your-account-inkweld-preview.workers.dev
ℹ️ Production: https://your-account-inkweld.workers.dev
✅ Generated environment.preview.ts
✅ Generated environment.cloudflare.ts
Step 5: Run Migrations
Apply the database schema:
============================================================
Running Database Migrations
============================================================
Run database migrations now? (y/n): y
ℹ️ Running migrations on inkweld_preview...
✅ Preview database migrated
ℹ️ Running migrations on inkweld_prod...
✅ Production database migrated
Step 6: Set Secrets
Configure sensitive values securely:
============================================================
Setting Secrets
============================================================
ℹ️ SESSION_SECRET is required for each environment.
ℹ️ This is a cryptographic key used to sign session cookies.
⚠️ CRITICAL: If this key is used for database encryption, changing it will
make existing data unreadable!
Generate and set SESSION_SECRET automatically? (y/n): y
✅ SESSION_SECRET set for preview
✅ SESSION_SECRET set for production
Deploy Your Application
After setup, deploy with:
# Deploy to preview (for testing)
npm run cloudflare:preview:deploy
# Deploy to production
npm run cloudflare:prod:deploy
The deploy commands:
- Build the Angular frontend with the correct environment
- Build the Cloudflare Worker backend
- Run any pending database migrations
- Deploy both frontend and backend
:::info About Preview Environments Inkweld provides one shared preview environment and one production environment, plus optional per-PR frontend previews triggered by a label.
The shared preview environment is what cloudflare:preview:deploy deploys to. It always points at your custom (or workers.dev) domain, so it can only show one branch at a time. This is the environment used for testing feature branches before merging and, via the deploy-cloudflare.yml workflow, the latest main.
Per-PR previews are separate. When you add the deploy:preview:frontend label to a pull request, GitHub Actions deploys the frontend to Cloudflare Pages under a stable pr-<number> branch, giving each PR a predictable URL:
- Frontend:
https://pr-<number>.inkweld-frontend-preview.pages.dev
Per-PR previews are frontend-only — no backend is provisioned. The build ships with an empty apiUrl, so the app starts in local/offline mode; you can point it at an existing server at runtime through the setup flow. This keeps the preview lightweight and avoids exposing any Cloudflare account details. When the PR is closed or the deploy:preview:frontend label is removed, a cleanup workflow attempts to delete this branch's Pages deployments. See Per-PR previews below.
Manual deployments always go live: When you run cloudflare:preview:deploy or cloudflare:prod:deploy from any branch, the deployment will immediately go live on your custom domain. A warning is shown if you're not on the main branch, but the deployment proceeds normally.
For a typical workflow:
- Use preview environment for testing feature branches before merging
- Use per-PR previews to review many PRs in parallel without stepping on each other
- Use production environment for your live application from
main:::
Per-PR Previews
Per-PR previews give every labeled pull request its own frontend deployment on Cloudflare Pages so you can review many PRs at once.
How it works
- Adding the
deploy:preview:frontendlabel to a PR triggers.github/workflows/deploy-pr-preview.yml. - The workflow writes a frontend-only
environment.preview.ts(emptyapiUrl/wssUrl), builds the Angular app, and deploys it to Cloudflare Pages on thepr-<number>branch (stable URL). - It comments the preview URL back on the PR.
.github/workflows/cleanup-pr-preview.ymllists that branch's Pages deployments and deletes each one when the PR is closed or the label is removed. Cloudflare does not allow deleting the latest deployment for a branch, so the branch's most recent preview URL may remain reachable after cleanup.
Required configuration
Per-PR previews use the same Cloudflare secrets as the shared preview (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID). No extra variables are required.
Optionally override the Pages project name with a PAGES_PROJECT variable (default: inkweld-frontend-preview). The per-PR frontend URL is derived as https://pr-<number>.<PAGES_PROJECT>.pages.dev.
Usage
- Add the
deploy:preview:frontendlabel to a pull request. - GitHub Actions deploys a frontend-only preview and comments the URL on the PR.
- Review the preview. Multiple PRs can be deployed at once.
- Remove the label or close the PR to trigger the cleanup workflow, which attempts to delete the branch's Pages deployments. Because Cloudflare keeps the latest deployment for a branch, the most recent preview URL may remain reachable.
Note: Per-PR previews are frontend-only. They start in local/offline mode; to exercise backend features, point the preview at an existing server at runtime through the setup flow. No data is persisted by the preview itself.
Manual Setup
If you prefer manual configuration, see the detailed steps below.
1. Login to Cloudflare
cd backend
bun run wrangler login
2. Create D1 Databases
bun run wrangler d1 create inkweld_preview
bun run wrangler d1 create inkweld_prod
Note the database_id values from the output.
3. Configure wrangler.toml
cp wrangler.toml.example wrangler.toml
Edit wrangler.toml and update the database IDs:
[[env.preview.d1_databases]]
binding = "DB"
database_name = "inkweld_preview"
database_id = "your-preview-database-id"
migrations_dir = "drizzle"
[[env.production.d1_databases]]
binding = "DB"
database_name = "inkweld_prod"
database_id = "your-production-database-id"
migrations_dir = "drizzle"
4. Run Migrations
bun run db:migrate:preview
bun run db:migrate:prod
5. Set Secrets
bun run wrangler secret put SESSION_SECRET --env preview
bun run wrangler secret put SESSION_SECRET --env production
6. Deploy
npm run cloudflare:preview:deploy
npm run cloudflare:prod:deploy
Automated Deployment via GitHub Actions
The .github/workflows/deploy-cloudflare.yml workflow deploys the preview environment automatically on every push to main. To enable it, configure the following in your fork's Settings → Secrets and variables → Actions.
Required GitHub variables (visible, editable)
These hold non-secret configuration. Using a variable (not a secret) means you can read and tweak the value in the GitHub UI without a round-trip through a secret manager.
| Variable | Purpose |
|---|---|
BACKEND_WRANGLER_TOML | The full contents of backend/wrangler.toml for your deployment — D1 IDs, R2 bucket names, custom domains, ALLOWED_ORIGINS, etc. Use SESSION_SECRET = "placeholder-set-via-wrangler-secret" in the [vars] block; the real value is injected from the SESSION_SECRET GitHub secret at deploy time (see below). Include a [env.preview.observability] enabled = true block so Workers Logs stay on across redeploys — without it, every wrangler deploy resets observability to off. |
BASE_URL | Public backend URL, e.g. https://api.preview.inkweld.app |
FRONTEND_URL | Public frontend URL, e.g. https://preview.inkweld.app |
APP_NAME | Display name shown in the UI, e.g. Inkweld |
TWA_SHA256_FINGERPRINT | (Optional) Android TWA signing fingerprint for assetlinks.json |
Required GitHub secrets (hidden)
| Secret | Purpose |
|---|---|
CLOUDFLARE_API_TOKEN | Wrangler API token with Workers/Pages/D1/R2 permissions |
CLOUDFLARE_ACCOUNT_ID | Your Cloudflare account ID |
SESSION_SECRET | The real session-signing key (32+ characters). Optional when SESSION_SECRET is already set as a Cloudflare Worker secret (via wrangler secret put) — in that case it persists across deploys and no GitHub secret is needed. When provided, CI injects it via wrangler deploy --secrets-file, which atomically overrides the placeholder [vars] value. |
PREVIEW_API_URL | Frontend environment file: API base URL |
PREVIEW_WSS_URL | Frontend environment file: WebSocket URL for Yjs sync |
How the deploy works
- CI writes
backend/wrangler.tomlfrom theBACKEND_WRANGLER_TOMLvariable. - If a
SESSION_SECRETGitHub secret exists, CI writes a temporarybackend/.secrets.envcontaining it. wrangler deploy --env preview …pushes the Worker. The--secrets-file .secrets.envflag is only added when the file exists — wrangler rejects an empty secrets file, so when no GitHub secret is set the deploy falls back to the existing Cloudflare Worker secret (set once viawrangler secret put), which persists across deploys and overrides the[vars]placeholder at runtime. Cloudflare's secret store has no difference to your Worker between a secret and an environment variable, and secrets are never deleted by a deployment.
Why not keep the whole toml as a secret?
Earlier versions stored the entire wrangler.toml in a single GitHub secret. That worked but was awkward: every tweak (toggling observability, changing a domain, bumping an origin) required editing an opaque blob in the secret manager, and there was no way to read the current value back. The current split keeps the non-secret toml in a visible variable and pushes only the genuinely sensitive SESSION_SECRET through Cloudflare's secret mechanism — which is what Cloudflare's own docs recommend ("Do not use vars to store sensitive information").
Migrating from the old single-secret setup
If you previously set BACKEND_WRANGLER_TOML as a secret:
- Copy the secret's current value.
- Replace any real
SESSION_SECRET = "…"line withSESSION_SECRET = "placeholder-set-via-wrangler-secret". - Add
[env.preview.observability] enabled = trueso observability stays on. TOML ordering matters: place the[env.preview.observability]subtable after anyroutes = [...]line and before the next[env.*]section — keys that appear after a[table]header belong to that table, soroutesplaced after[env.preview.observability]would be parsed as a (invalid) field ofobservabilityrather than ofenv.preview. - Create a GitHub variable named
BACKEND_WRANGLER_TOMLwith the edited contents. - Create a GitHub secret named
SESSION_SECRETwith the real session key (optional — see the secrets table above). - Delete the old
BACKEND_WRANGLER_TOMLsecret.
Custom Domain
To use your own domain instead of *.workers.dev and *.pages.dev:
Prerequisites
- Add your domain to Cloudflare - Transfer DNS or use Cloudflare as your DNS provider
- Verify domain ownership - Follow Cloudflare's verification steps
Using the Setup Wizard
The setup wizard prompts for custom domains for both preview and production environments:
Configure custom domains for preview? (y/n): y
Preview backend API domain (e.g., api.preview.yoursite.com): api.preview.inkweld.app
Preview frontend domain (e.g., preview.yoursite.com): preview.inkweld.app
Configure custom domains for production? (y/n): y
Production backend API domain (e.g., api.yoursite.com): api.inkweld.app
Production frontend domain (e.g., yoursite.com): inkweld.app
The wizard will:
- Configure backend custom domains via
routesinwrangler.toml(Workers support this) - Add frontend custom domains to
ALLOWED_ORIGINSfor CORS - Remind you to configure frontend custom domains in the Cloudflare Dashboard
Manual Configuration
Backend Custom Domain
Edit backend/wrangler.toml and uncomment/update the routes configuration:
# Preview
[env.preview]
name = "inkweld-backend-preview"
routes = [{ pattern = "api.preview.yoursite.com", custom_domain = true }]
# Production
[env.production]
name = "inkweld-backend-prod"
routes = [{ pattern = "api.yoursite.com", custom_domain = true }]
Or via Cloudflare Dashboard:
- Go to Workers & Pages → Your worker → Settings → Triggers
- Click Add Custom Domain
- Enter your domain (e.g.,
api.yoursite.com)
Frontend Custom Domain
Important: Unlike Workers, Cloudflare Pages does not support custom domain configuration via wrangler.toml. Custom domains for Pages must be configured in the Cloudflare Dashboard.
Via Cloudflare Dashboard (required for Pages):
- Go to Workers & Pages → Your Pages project → Custom domains
- Click Set up a custom domain
- Enter your domain (e.g.,
preview.yoursite.comoryoursite.com) - Follow the DNS verification steps
Update ALLOWED_ORIGINS
After setting up custom domains, update ALLOWED_ORIGINS in backend/wrangler.toml:
[env.preview.vars]
ALLOWED_ORIGINS = "https://preview.yoursite.com"
[env.production.vars]
ALLOWED_ORIGINS = "https://yoursite.com,https://www.yoursite.com"
Redeploy
After making changes, redeploy both frontend and backend:
# Preview
npm run cloudflare:preview:deploy
# Production
npm run cloudflare:prod:deploy
Free Tier Limits
Cloudflare's free tier is generous for development and small teams:
| Service | Free Tier Limit | Notes |
|---|---|---|
| Workers | 100K requests/day | 10ms CPU per request |
| D1 Database | 5M reads/day, 100K writes/day | 5GB storage |
| Durable Objects | 100K requests/day | 13K GB-seconds/day |
| R2 Storage | 10GB storage | Requires payment method |
| Pages | Unlimited sites | 500 builds/month |
:::info Understanding GB-seconds Durable Objects are billed by memory × time. 13,000 GB-seconds translates to roughly 21 hours of continuous real-time collaboration per day. :::
Workers Paid Plan
For production use, consider the Workers Paid plan at $5/month:
- 10 million requests/month (included)
- 30 seconds CPU time per request
- Higher D1 limits (25M reads, 50M writes/month)
- 400K GB-seconds Durable Objects/month
- Priority support
Monitoring
View Logs
# Stream real-time logs
bun run wrangler tail --env preview
bun run wrangler tail --env production
Analytics
In the Cloudflare dashboard:
- Go to Workers & Pages
- Select your worker
- Click Analytics for requests, errors, and performance
Troubleshooting
"Worker name already exists"
Worker names are globally unique. Choose a different name with your account prefix.
Database connection errors
Verify your database_id values in wrangler.toml match the actual D1 database IDs.
CORS errors
Update ALLOWED_ORIGINS to include all domains that need to access the API:
ALLOWED_ORIGINS = "https://yoursite.com,https://app.yoursite.com,https://preview.yoursite.com"
Real-time collaboration not working
Check that Durable Objects bindings are correctly configured and the worker deployed successfully.
Migration errors
Ensure migrations_dir = "drizzle" is set in all D1 database sections of your wrangler.toml.
MCP/AI assistant connections blocked
If AI assistants like Claude cannot connect to your MCP endpoint, check your Cloudflare Security > Bots settings:
- Go to Security → Bots in the Cloudflare dashboard
- Look for the Block AI Scrapers and Crawlers setting
- If enabled, this blocks bots categorized as "AI training crawlers" — which may include legitimate AI assistants using MCP
Solution: Either disable this setting, or add a WAF exception for the /api/v1/ai/mcp path:
- Go to Security → WAF → Custom rules
- Create an exception rule for paths starting with
/api/v1/ai/ - Skip the AI bot check for these paths
Disabling AI crawler blocking entirely exposes your site to content scraping for AI training. Using a path-specific exception is recommended.
Next Steps
- Configure your instance - Environment variables and customization
- Set up CI/CD - Automate deployments
- Admin Panel - Manage users and system health from the web UI