#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:
production environment secrets:
production environment variables:
#PlanetScale
-
Create a PlanetScale account and organization.
-
Create a new database with the Postgres engine:
pscale database create curl --region <REGION_SLUG> --engine postgres -
Create two roles on the
mainbranch.- App role for Hyperdrive and production. Select
pg_read_all_dataandpg_write_all_data. - Migrations role for CI. Select
postgresfor full DDL access used bykysely migrate.
- App role for Hyperdrive and production. Select
-
Record the connection strings:
postgres://<user>:<password>@<host>:5432/postgres?sslmode=require. -
Connect to Cloudflare Workers via the PlanetScale Hyperdrive integration and copy the Hyperdrive ID into
wrangler.jsoncunderenv.production.hyperdrive[0].id.
#Creating a Cloudflare API Token
-
Go to Cloudflare API Tokens.
-
Click “Create Token”.
-
Select “Create Custom Token”.
-
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
-
Set Account Resources to your account.
-
Set Zone Resources to your domain, for example
curl.md. -
Click “Continue to summary” and then “Create Token”.
Create a second Cloudflare token for Browser Rendering runtime access:
- Create another custom token.
- Add Account -> Browser Rendering -> Edit only.
- Scope it to the
curl.mdaccount. - Save it as
CLOUDFLARE_BROWSER_API_TOKENin GitHub Actions secrets and your local.env.
#GitHub App Setup
-
Click “New GitHub App”.
-
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
- GitHub App name:
-
Under Permissions, set Account permissions -> Email addresses -> Read-only.
-
Click “Create GitHub App”.
-
Copy the Client ID into
GH_CLIENT_ID. -
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.
- Go to Stripe Dashboard.
- Copy your Publishable key from API keys into
STRIPE_PUBLISHABLE_KEY. - Copy your Secret key from API keys into
STRIPE_SECRET_KEY. - 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:
- Create a Sentry project for JavaScript / Cloudflare.
- Copy
SENTRY_DSNfrom Project Settings -> Client Keys (DSN). - Create
SENTRY_AUTH_TOKENfrom Settings -> Developer Settings -> Auth Tokens.- Use an Organization Auth Token if available.
- Required permissions:
org:readandproject:releases. - A Personal Token also works with Project: Read & Write and Release: Admin permissions.
- Add
SENTRY_DSNandSENTRY_AUTH_TOKENas 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.
- Go to Edge Certificates for the
curl.mdzone. - Disable Always Use HTTPS.
- Enable HTTP Strict Transport Security (HSTS).
- Use these HSTS settings.
- Max Age Header:
6 months - Apply HSTS policy to subdomains:
off - Preload:
off - No-Sniff Header:
on
- Max Age Header:
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.
- Go to DNS Records for the
curl.mdzone. - Add a proxied A record:
www->192.0.2.1. - Go to Bulk Redirects.
- Create a bulk redirect list with
www.curl.md->https://curl.mdusing 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:
preview environment variables:
#PlanetScale Branching
-
Create a service token in PlanetScale with the following access on your database.
create_branchdelete_branchread_branchconnect_branch
-
Add these secrets to the
previewenvironment.
#Preview Stripe
Preview environments use Stripe test mode keys so no real charges occur.
- Copy your test mode secret key from API keys.
- Add
STRIPE_SECRET_KEYto thepreviewenvironment.
Last updated: May 5 3:02 PM