Skip to content

Overview

#Deploy

curl.md deploys on Cloudflare Workers and uses GitHub Actions for production and preview environments.

Note

This guide is mostly for the team working on curl.md itself. If you are self-hosting, you can still adapt the same patterns for your own Cloudflare, database, auth, and CI setup.

#Production

Production requires configuration across a few systems:

  • Cloudflare for Workers, routes, KV, queues, and related services
  • PlanetScale Postgres for the database
  • GitHub Environments and Actions secrets for CI-driven deploys
  • Stripe and GitHub OAuth for billing and auth flows

#GitHub Actions Secrets

Secrets are managed via GitHub Environments and repo-level secrets.

Repository secrets - shared across all environments:

NamePurpose
CLOUDFLARE_ACCOUNT_IDCloudflare account ID. Used by CI, deploys, and Browser Rendering fallback at runtime.
CLOUDFLARE_API_TOKENCloudflare API token for deploys and admin scripts. See Creating a Cloudflare API Token.
CLOUDFLARE_BROWSER_API_TOKENCloudflare Browser Rendering runtime token. Scope it to Browser Rendering - Edit on the curl.md account only.
COOKIE_SECRETSecret for signing session cookies. Generate with openssl rand -base64 32.
GH_CLIENT_IDGitHub App client ID. See GitHub App Setup.
GH_CLIENT_SECRETGitHub App client secret. See GitHub App Setup.
SENTRY_DSNSentry DSN for error tracking. See Sentry.
SENTRY_AUTH_TOKENSentry auth token for release creation and source map uploads. See Sentry.
TOKEN_ENCRYPTION_KEYBase64-encoded 256-bit key for encrypting OAuth tokens. Generate with openssl rand -base64 32.

production environment secrets:

NamePurpose
DB_URLPlanetScale Postgres connection string for production migrations.
STRIPE_SECRET_KEYStripe live secret key. See Stripe Setup.
STRIPE_WEBHOOK_SECRETStripe webhook signing secret. See Stripe Setup.

production environment variables:

NamePurpose
STRIPE_PUBLISHABLE_KEYStripe live publishable key (pk_live_...). See Stripe Setup.

#PlanetScale

  1. Create a PlanetScale account and organization.

  2. Create a new database with the Postgres engine:

    pscale database create curl --region <REGION_SLUG> --engine postgres
  3. Create two roles on the main branch.

    • App role for Hyperdrive and production. Select pg_read_all_data and pg_write_all_data.
    • Migrations role for CI. Select postgres for full DDL access used by kysely migrate.
  4. Record the connection strings: postgres://<user>:<password>@<host>:5432/postgres?sslmode=require.

  5. Connect to Cloudflare Workers via the PlanetScale Hyperdrive integration and copy the Hyperdrive ID into wrangler.jsonc under env.production.hyperdrive[0].id.

#Creating a Cloudflare API Token

  1. Go to Cloudflare API Tokens.

  2. Click “Create Token”.

  3. Select “Create Custom Token”.

  4. Add these permissions.

    • Account -> Browser Rendering -> Edit
    • Account -> Workers AI -> Edit
    • Account -> Queues -> Edit
    • Account -> Hyperdrive -> Edit
    • Account -> Workers KV Storage -> Edit
    • Account -> Workers Scripts -> Edit
    • Zone -> Zone WAF -> Edit
    • Zone -> Workers Routes -> Edit
  5. Set Account Resources to your account.

  6. Set Zone Resources to your domain, for example curl.md.

  7. Click “Continue to summary” and then “Create Token”.

Create a second Cloudflare token for Browser Rendering runtime access:

  1. Create another custom token.
  2. Add Account -> Browser Rendering -> Edit only.
  3. Scope it to the curl.md account.
  4. Save it as CLOUDFLARE_BROWSER_API_TOKEN in GitHub Actions secrets and your local .env.

#GitHub App Setup

  1. Go to GitHub Developer Settings -> GitHub Apps.

  2. Click “New GitHub App”.

  3. Fill in:

    • GitHub App name: curl.md
    • Homepage URL: https://curl.md
    • Callback URL: https://curl.md/api/auth/github/callback
    • Check “Expire user authorization tokens”
    • Check “Enable Device Flow” for CLI authentication
    • Uncheck “Active” under Webhook
  4. Under Permissions, set Account permissions -> Email addresses -> Read-only.

  5. Click “Create GitHub App”.

  6. Copy the Client ID into GH_CLIENT_ID.

  7. Generate a new client secret and copy it into GH_CLIENT_SECRET.

#Stripe Setup

Stripe powers prepaid credit billing. A single Stripe account is used with test mode for development and live mode for production.

  1. Go to Stripe Dashboard.
  2. Copy your Publishable key from API keys into STRIPE_PUBLISHABLE_KEY.
  3. Copy your Secret key from API keys into STRIPE_SECRET_KEY.
  4. Set the callback webhook URL and get the webhook secret.
    • Go to Webhooks and click “Add endpoint”.
    • Set URL to https://curl.md/api/stripe/webhook.
    • Select events: payment_intent.succeeded, charge.dispute.created, refund.created.
    • Copy the signing secret into STRIPE_WEBHOOK_SECRET.

#Sentry

Error tracking covers server-side Workers, the Hono API, and the React client. CI also uploads production and preview source maps to Sentry during pnpm build.

The app uses these Sentry values:

NameWhere it is usedRequired
SENTRY_DSNWorker runtime, browser client, and tunnel targetYes
SENTRY_AUTH_TOKENBuild-time release creation and source map uploadYes for source maps
SENTRY_ORGBuild-time source map uploadNo, defaults to wevm
SENTRY_PROJECTBuild-time source map uploadNo, defaults to curl_md
  1. Create a Sentry project for JavaScript / Cloudflare.
  2. Copy SENTRY_DSN from Project Settings -> Client Keys (DSN).
  3. Create SENTRY_AUTH_TOKEN from Settings -> Developer Settings -> Auth Tokens.
    • Use an Organization Auth Token if available.
    • Required permissions: org:read and project:releases.
    • A Personal Token also works with Project: Read & Write and Release: Admin permissions.
  4. Add SENTRY_DSN and SENTRY_AUTH_TOKEN as GitHub repository secrets.

SENTRY_ORG and SENTRY_PROJECT are hardcoded to wevm and curl_md in vite.config.ts. Override them only when self-hosting or uploading to a different Sentry project.

If SENTRY_AUTH_TOKEN is missing, builds still pass, but Sentry releases and source maps are not uploaded.

#HTTPS Enforcement

Keep Cloudflare's blanket HTTP-to-HTTPS redirect off so bare curl curl.md/<url> requests reach the Worker. The Worker serves unauthenticated URL-fetch paths over HTTP and redirects all other HTTP routes to HTTPS.

  1. Go to Edge Certificates for the curl.md zone.
  2. Disable Always Use HTTPS.
  3. Enable HTTP Strict Transport Security (HSTS).
  4. Use these HSTS settings.
    • Max Age Header: 6 months
    • Apply HSTS policy to subdomains: off
    • Preload: off
    • No-Sniff Header: on

Keep includeSubDomains off unless every current and future *.curl.md hostname should be HTTPS-only. Preview environments use *.curl.md custom domains, so enabling includeSubDomains would apply HSTS to them too.

#WWW Redirect

Redirect www.curl.md to curl.md via Bulk Redirects.

  1. Go to DNS Records for the curl.md zone.
  2. Add a proxied A record: www -> 192.0.2.1.
  3. Go to Bulk Redirects.
  4. Create a bulk redirect list with www.curl.md -> https://curl.md using a 301, preserve query string, subpath matching, and preserve path suffix.

#Preview

Preview environments deploy per PR with isolated PlanetScale database branches and Cloudflare Hyperdrive configs. On PR close or draft, cleanup deletes the Worker, Hyperdrive config, PlanetScale branch, KV namespace, Queues, and Stripe webhook. A daily sweep catches orphans.

#GitHub Environments

Preview secrets are scoped via GitHub Environments.

preview environment secrets:

NamePurpose
PLANETSCALE_SERVICE_TOKENPlanetScale service token. See PlanetScale Branching.
PLANETSCALE_SERVICE_TOKEN_IDPlanetScale service token ID. See PlanetScale Branching.
PLANETSCALE_ORGPlanetScale organization slug, for example wevm.
PLANETSCALE_DBPlanetScale database name, same as production, for example curl.
STRIPE_SECRET_KEYStripe test mode secret key. See Preview Stripe.

preview environment variables:

NamePurpose
STRIPE_PUBLISHABLE_KEYStripe test mode publishable key (pk_test_...).

#PlanetScale Branching

  1. Create a service token in PlanetScale with the following access on your database.

    • create_branch
    • delete_branch
    • read_branch
    • connect_branch
  2. Add these secrets to the preview environment.

    NamePurpose
    PLANETSCALE_SERVICE_TOKENThe token value.
    PLANETSCALE_SERVICE_TOKEN_IDThe token ID.
    PLANETSCALE_ORGYour PlanetScale organization slug, for example wevm.
    PLANETSCALE_DBYour database name, same as production, for example curl.

#Preview Stripe

Preview environments use Stripe test mode keys so no real charges occur.

  1. Copy your test mode secret key from API keys.
  2. Add STRIPE_SECRET_KEY to the preview environment.