Cloudflare deployment
The full Cloudflare stack is supported as of v0.2. This guide walks through a first-time production deployment.
Topology
Section titled “Topology”| Component | Service |
|---|---|
Product app (app.marrow.so) | Cloudflare Workers (@opennextjs/cloudflare) |
Marketing site (marrow.so) | Cloudflare Pages (static export) |
Docs site (docs.marrow.so) | Cloudflare Pages (static Astro) |
Backend API (api.marrow.so) | Cloudflare Containers (image from GHCR) |
| Database | Neon Postgres (free tier) |
| Attachments | Cloudflare R2 |
| Auth | Auth0 (GitHub + Google social connections) |
| DNS | Cloudflare DNS (marrow.so) |
Prerequisites
Section titled “Prerequisites”- A Cloudflare account with Workers and Containers enabled.
wranglerCLI installed and authenticated (npm i -g wrangler && wrangler login).- A Neon project with a Postgres database.
- An Auth0 account (free tier: 7,500 MAU).
- A Stripe account (for billing).
- GitHub secrets
CLOUDFLARE_API_TOKENandCLOUDFLARE_ACCOUNT_IDset in your repo.
1. Cloudflare API token
Section titled “1. Cloudflare API token”In the Cloudflare dashboard → Profile → API Tokens → Create Token, use the Edit Cloudflare Workers template and add:
Zone:DNS:EditPages:Edit
Save the token as the CLOUDFLARE_API_TOKEN GitHub secret. Your account ID (visible in the right sidebar of the dashboard) goes in CLOUDFLARE_ACCOUNT_ID.
2. Neon Postgres
Section titled “2. Neon Postgres”Create a project at neon.tech. Copy the pooled connection string — it looks like:
postgresql://neondb_owner:<password>@<host>.neon.tech/neondb?sslmode=requireThis becomes the DATABASE_URL wrangler secret.
3. R2 bucket
Section titled “3. R2 bucket”wrangler r2 bucket create marrow-attachmentsThen create an R2 API token in the Cloudflare dashboard → R2 → Manage R2 API Tokens with Object Read & Write on the marrow-attachments bucket. Note the Access Key ID, Secret Access Key, and endpoint URL (https://<account-id>.r2.cloudflarestorage.com).
4. Auth0 (GitHub + Google sign-in)
Section titled “4. Auth0 (GitHub + Google sign-in)”Auth0 acts as a single OIDC issuer and lets users sign in with GitHub or Google.
- Create an account at auth0.com. Create a Regular Web Application.
- In Settings:
- Allowed Callback URLs:
https://api.marrow.so/api/auth/callback - Allowed Logout URLs:
https://app.marrow.so
- Allowed Callback URLs:
- Enable GitHub — Authentication → Social → GitHub. Create a GitHub OAuth app:
- Homepage URL:
https://marrow.so - Callback URL:
https://<your-auth0-domain>/login/callback
- Homepage URL:
- Enable Google — Authentication → Social → Google (Auth0 dev keys work for testing; swap in your own Google OAuth app for production).
- Note your Domain (
<tenant>.us.auth0.com), Client ID, and Client Secret.
5. Stripe
Section titled “5. Stripe”Create products and prices for your tiers in the Stripe dashboard. After the API is deployed, register a webhook at https://api.marrow.so/api/billing/webhook for events: checkout.session.completed, customer.subscription.updated, customer.subscription.deleted, invoice.payment_failed.
6. Configure API wrangler secrets
Section titled “6. Configure API wrangler secrets”Run from the api/ directory:
wrangler secret put SECRET_KEY # 64-char random stringwrangler secret put DATABASE_URL # Neon pooled connection stringwrangler secret put R2_ENDPOINT_URL # https://<account-id>.r2.cloudflarestorage.comwrangler secret put R2_ACCESS_KEY_IDwrangler secret put R2_SECRET_ACCESS_KEYwrangler secret put R2_BUCKET # marrow-attachmentswrangler secret put OIDC_CLIENT_SECRET # Auth0 client secretwrangler secret put STRIPE_SECRET_KEYwrangler secret put STRIPE_WEBHOOK_SECRET # from Stripe webhook step above7. Update api/wrangler.toml vars
Section titled “7. Update api/wrangler.toml vars”Edit api/wrangler.toml and fill in the non-secret vars:
[vars]OIDC_ISSUER = "https://<your-auth0-domain>/" # trailing slash requiredOIDC_CLIENT_ID = "<auth0-client-id>"OIDC_REDIRECT_URI = "https://api.marrow.so/api/auth/callback"CORS_ORIGINS = "https://app.marrow.so"FRONTEND_URL = "https://app.marrow.so"COOKIE_DOMAIN = ".marrow.so"STORAGE_BACKEND = "r2"SAAS_MODE = "true"STRIPE_STARTER_PRICE_MONTHLY = "price_..."STRIPE_STARTER_PRICE_YEARLY = "price_..."STRIPE_BUSINESS_PRICE_MONTHLY = "price_..."STRIPE_BUSINESS_PRICE_YEARLY = "price_..."STRIPE_GROWTH_PRICE_MONTHLY = "price_..."STRIPE_GROWTH_PRICE_YEARLY = "price_..."8. Create Cloudflare Pages projects
Section titled “8. Create Cloudflare Pages projects”# Marketing sitewrangler pages project create marrow-marketing --production-branch main
# Docs sitewrangler pages project create marrow-docs --production-branch mainThe web Worker project is created automatically on first deploy.
9. DNS records
Section titled “9. DNS records”In the Cloudflare dashboard → marrow.so → DNS, add:
| Name | Type | Target | Proxy |
|---|---|---|---|
@ (root) | CNAME | marrow-marketing.pages.dev | Proxied |
www | CNAME | marrow-marketing.pages.dev | Proxied |
app | CNAME | marrow-web.<account>.workers.dev | Proxied |
docs | CNAME | marrow-docs.pages.dev | Proxied |
The api.marrow.so subdomain is configured automatically when you run wrangler deploy from api/.
Then add each subdomain as a custom domain in the respective Pages / Workers dashboard.
10. Deploy
Section titled “10. Deploy”Merge your branch to main and tag the release:
git tag v0.2.0 && git push origin v0.2.0GitHub Actions (release.yml) will:
- Build and push the API container image to GHCR.
- Deploy the API via
wrangler deploy(Cloudflare Containers). - Build the web app with OpenNext and deploy it as a Cloudflare Worker.
The marketing workflow (marketing.yml) deploys the static site on any push to main that touches web-marketing/.
11. Post-deploy: Marrow org billing exemption
Section titled “11. Post-deploy: Marrow org billing exemption”After your first sign-in at app.marrow.so, your personal org is auto-created. Set it to the enterprise tier (all features, no billing) via the Neon SQL editor:
UPDATE organizations SET tier = 'enterprise' WHERE slug = '<your-org-slug>';