RDCO Web-App Deployment Standard
The standard pattern for deploying every Ray Data Co web property — Sanity Check (sc.raydata.co), Squarely (squarely-web), the eventual ray-data-co marketing rebuild, hq.raydata.co, and any future RDCO web app.
The pattern in one sentence
GitHub Actions builds + deploys to Cloudflare Pages on push to main, using CLOUDFLARE_API_TOKEN + CLOUDFLARE_ACCOUNT_ID as repo-level Secrets. No local CF dependency. No 1Password lookup at deploy time. No founder-laptop-must-be-awake dependency.
Why this pattern
- Same secret store, same lifecycle. When the CF token rotates, every repo updates from the same source — GitHub Settings → Secrets per repo. No
~/.envfiles to chase, no 1Password entries to keep in sync. - No agent-local CF auth needed. Ray (the agent) doesn’t need a working
wrangler whoamito ship code. Push to a branch → CI builds and deploys → done. - Preview-per-branch comes free. The branch-alias step in the workflow turns every non-main branch into its own
<safe-branch>.<project>.pages.devpreview URL. Founder reviews from his phone, doesn’t need to spin up a dev server. - Diagnosable. If a deploy fails, the GH Actions log is the artifact — single timeline, no “did wrangler pick up the right token?” mystery.
The canonical workflow file
Located at .github/workflows/deploy.yml in every web-app repo. Source of truth: rdco-sc deploy.yml.
Standard structure (per-app changes ONLY in the marked spots):
name: Deploy to Cloudflare Pages
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
# Image optimization step — keep verbatim, threshold 50KB. WebP at quality 80.
- name: Install cwebp
run: |
if ! command -v cwebp &>/dev/null; then
sudo apt-get update -qq && sudo apt-get install -y -qq webp
fi
- name: Optimize images (PNG/JPG → WebP)
run: |
# ... (see canonical file for full bash) ...
- name: Build
env:
# APP-SPECIFIC: env vars the build needs at compile time, sourced from Secrets.
# SC needs PUBLIC_TURNSTILE_SITE_KEY. Squarely currently needs none.
# PUBLIC_TURNSTILE_SITE_KEY: ${{ secrets.PUBLIC_TURNSTILE_SITE_KEY }}
run: bun run build
- name: Compute CF Pages branch alias
id: alias
run: |
BRANCH="${GITHUB_REF_NAME}"
SAFE=$(echo "$BRANCH" | tr '[:upper:]' '[:lower:]' | tr '/_' '-' | sed 's/[^a-z0-9-]//g')
echo "branch=$SAFE" >> "$GITHUB_OUTPUT"
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
# APP-SPECIFIC: --project-name matches the CF Pages project name.
# Convention: project name == GitHub repo name.
command: pages deploy dist --project-name=<APP-NAME> --branch=${{ steps.alias.outputs.branch }}
Per-app variation (only these are allowed to differ)
--project-name— must match the Cloudflare Pages project name. Convention: same as the GitHub repo name (e.g.sc-raydata-co,squarely-web).- Build env vars — only the app-specific build-time secrets. SC needs
PUBLIC_TURNSTILE_SITE_KEY. Squarely doesn’t (yet). The eventual ConvertKit form-action wiring may needPUBLIC_CONVERTKIT_FORM_IDfor Squarely. - Build command — currently always
bun run build. If a repo uses a different package manager, change here. Otherwise leave alone.
Everything else stays verbatim across repos. If you find yourself wanting to vary something else, file an SOP-amendment proposal first — drift is the enemy.
Required GH repo Secrets
Every repo using this pattern must have these two Secrets set in Settings → Secrets and variables → Actions:
| Secret name | Source | Notes |
|---|---|---|
CLOUDFLARE_API_TOKEN | CF dashboard → My Profile → API Tokens. Permissions: Account.Cloudflare Pages: Edit, Zone.Cache Purge: Purge (optional, for SC’s purge step) | One token per Cloudflare account is sufficient. Rotate yearly. |
CLOUDFLARE_ACCOUNT_ID | CF dashboard → any zone → right sidebar | Static. 32-character hex string. |
Plus any app-specific env vars listed in the workflow’s Build step.
Standard repo bootstrap checklist
When scaffolding a new RDCO web app:
- Create the repo under
RayDataCo/<app-name>— private by default, public if it’s a marketing surface. - Set the local git config inside the repo:
git config user.name "Ray Ray" && git config user.email "ray@raydata.co". Otherwise commits attribute to whatever the global config or the previous repo’s local config says. This is the standard committer identity for all RDCO repos. Founder commits attribute to him via his own setup. - Copy
.github/workflows/deploy.ymlfromrdco-sc(or another existing RDCO repo) and modify ONLY the per-app variation spots noted above. - In GH repo settings, paste the two Cloudflare secrets. Copy them from rdco-sc’s Settings page (founder action, one-time per repo).
- In Cloudflare dashboard, create the Pages project with the matching name. Don’t connect it to GitHub on the CF side — the deploy workflow handles all pushes via the API. Connecting the GH integration in CF would create a second deploy path and conflict with the workflow.
- Push to main → workflow fires → first deploy creates the pages.dev URL.
- Wire the custom domain in CF Pages (founder action, one-time per app).
Anti-patterns to avoid
- Local-machine deploys via 1Password-injected tokens. Tried with Squarely 2026-04-25, three rounds of broken token entries before recommending this standard. The 1Password entry path is fragile — wrong field, format drift, keychain caching issues. GH Secrets eliminate the entire class.
- Per-repo committer identity drift. Squarely-web first commit attributed to “Ray (Claude) ben@raydata.co” because of a bad local git config — the email matched the founder’s verified GH email so the commit displayed on his account, not Ray-Ray-RayDCO’s. Standard committer identity (Ray Ray, ray@raydata.co) prevents this.
wrangler loginOAuth on a single Mac. Works, but ties the deploy to that Mac’s session. Doesn’t survivewrangler logout, browser cookie clear, or a new machine. GH Secrets are durable.- Connecting the CF Pages GitHub integration AND running this workflow. Creates two deploy paths racing on every push. Pick one. We pick the workflow.
Apps currently following this standard
| App | Repo | CF project | Status |
|---|---|---|---|
| Sanity Check | RayDataCo/sc-raydata-co | sc-raydata-co | ✅ live, deploys cleanly since 2026-04-25 |
| Squarely | RayDataCo/squarely-web | squarely-web (TBD by founder) | 🚧 workflow added 2026-04-26, awaiting founder Secrets paste + CF project create |
| Ray Data Co marketing | RayDataCo/rdco-website | TBD | future |
| HQ dashboard | RayDataCo/hq-raydata-co (private) | hq-raydata-co | future, may differ if Workers needed instead of Pages |
Related
- ../06-reference/2026-04-25-squarely-current-state-review — surface review that triggered the squarely-web build
- 2026-04-19-newsletter-output-invariants — same SOP-as-contract pattern, different domain (vault hygiene vs deploy)
- ../04-tooling/ — tool-decision artifacts