Emdash CMS Cloudflare Free Tier Production Playbook

Deploy EmDash on Cloudflare Free plan with a reliable path, clear feature boundaries, and practical rollback guidance.

Executive summary

You can run EmDash in production on Cloudflare Free plan for core CMS workflows.
What you must accept: sandboxed plugin execution is unavailable, so worker_loaders must be removed from configuration.

This guide is optimized for stability first, not maximum feature surface.

Capability boundaries on Free plan

Available

  • EmDash core site and admin flows
  • D1-backed content data
  • R2-backed media storage (within free usage)
  • Workers deployment and routing

Not available

  • Dynamic Workers for sandboxed plugins
  • plugin marketplace-style untrusted runtime execution

If your launch depends on sandboxed third-party plugin execution, Free plan is not sufficient.

Pre-deployment checklist

Complete all checks before running deployment commands:

  • Wrangler is authenticated (wrangler login already completed)
  • project builds locally without cloud-only assumptions
  • wrangler.jsonc has D1 and R2 bindings aligned with runtime config
  • worker_loaders entry is removed for Free plan
  • package manager choice is consistent (npm or pnpm, no mixing)

Resource provisioning order

Provision resources in this exact order to reduce binding churn:

  1. Create D1 database
  2. Create or enable R2 and then create bucket
  3. Update wrangler.jsonc with exact names and IDs
  4. Build and deploy Worker

Reason: D1 ID and bucket names become your source of truth for environment bindings.

# Provision D1 first
npx wrangler d1 create your-db-name

# Then provision R2 bucket
npx wrangler r2 bucket create your-media-bucket

Minimal wrangler.jsonc shape for Free plan

The following skeleton is intentionally small:

{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "your-site-name",
  "main": "./src/worker.ts",
  "compatibility_date": "2026-04-08",
  "compatibility_flags": ["nodejs_compat"],
  "d1_databases": [
    {
      "binding": "DB",
      "database_name": "your-db-name",
      "database_id": "replace-with-d1-id"
    }
  ],
  "r2_buckets": [
    {
      "binding": "MEDIA",
      "bucket_name": "your-media-bucket"
    }
  ]
}

No worker_loaders section on Free plan.

Build and deploy flow

Run commands from your EmDash project root:

# choose one package manager and stick to it
npm run build
npm run deploy

If your project is pnpm-based:

pnpm build
pnpm deploy

Avoid cross-running npm and pnpm in the same deployment session unless lockfiles are intentionally synchronized.

Post-deploy validation checklist

Validate in this order:

  1. Frontend URL resolves and returns expected content
  2. Admin URL (/_emdash/admin) is reachable
  3. first admin setup completes successfully
  4. create and publish one test item
  5. upload one media file and confirm retrieval

A deployment is not complete until data write paths are verified, not just route reachability.

High-frequency failure modes and shortest fixes

Failure: deployment succeeds, admin fails to initialize

Likely cause: binding mismatch between runtime integration and wrangler.jsonc.

Fix path:

  1. verify binding names (DB, MEDIA) are identical everywhere
  2. redeploy after correcting config
  3. retry admin initialization

Failure: R2 command blocked by account prompt

Likely cause: R2 was not enabled in dashboard yet.

Fix path:

  1. enable R2 in Cloudflare dashboard
  2. accept billing terms (free usage still applies)
  3. recreate bucket command

Likely cause: leftover sandbox configuration.

Fix path:

  1. remove worker_loaders
  2. disable sandbox-specific plugin setup
  3. redeploy and retest

Rollback strategy

Use a conservative rollback policy:

  • keep last known good deploy commit tagged
  • if production regression appears, redeploy previous tagged commit
  • postpone schema or plugin changes until traffic stabilizes

Operationally, fast rollback beats deep hotfix during incident windows.

When to upgrade from Free to Paid

Upgrade only when one of these is true:

  • you need sandboxed untrusted plugin execution
  • your traffic or storage regularly exceeds free limits
  • governance requires stronger plugin runtime isolation

Do not upgrade because of ambiguous UI wording in billing screens. Upgrade only for concrete capability requirements.