Namaste Ji by Ayushman Dash

Docs / ci-cd.md · mirrored from the repo

Namaste Ji — CI/CD & deploy strategy

Status: design + initial setup. How a growing monorepo builds, tests, and deploys many independent targets on Cloudflare — without rebuilding/redeploying everything on every push. Aligns with the documented GitHub Actions + Alchemy IaC direction (BACK-OFFICE.md).

The problem

Cloudflare Pages’ native Git integration rebuilds + redeploys on every push to the production branch, regardless of what changed. In a monorepo with many deployables (consumer app, back-office console, Workers/services, the brand site, …) that is wasteful and wrong: a push to services/api must not redeploy the brand page.

Principles

  1. CI is the deployer — not the platform’s Git integration. All deploys run through GitHub Actions → Wrangler. Pages projects are Direct Upload (Git auto-build OFF), so CI is the single, path-scoped deploy path — uniform across Pages and Workers.
  2. Deploy only what changed. Each target deploys only when its paths change (paths: filters now → Turborepo --affected once JS/TS apps land).
  3. One target = one deploy unit, independently versioned and deployed.
  4. Environments + previews. PRs → preview deploys (per-PR URLs); main → production; environment protection / manual approval where it matters.
  5. Resources as code (IaC). D1 / R2 / KV / Vectorize / Pages projects + bindings are declared via Alchemy, not click-ops — reproducible across staging/prod.

The model (look-ahead)

push / PR
  └─ GitHub Actions
       ├─ detect changed targets   (paths: filters now → Turbo `--affected` at scale)
       └─ per affected target: build + test (Turbo cache) → wrangler deploy
            • apps/web      → consumer app   (Pages/Workers)
            • apps/console  → back office     (Pages/Workers)
            • apps/brand    → brand book      (Pages)   ← first instance
            • services/*    → Workers
  └─ Alchemy reconciles infra (resources + bindings) per environment
  • Turborepo (when the JS monorepo exists): turbo run build test deploy --affected + remote cache → only affected packages run; stays fast as the repo grows.
  • cloudflare/wrangler-action deploys each target; secrets live in GitHub Actions (CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID).
  • Per-target workflows (or one matrix workflow keyed on changed paths) keep triggers explicit and scoped.

First instance — the brand site (set up now)

  • Deploys via .github/workflows/deploy-brand.yml, triggered only when index.html or docs/brand/** change (plus manual workflow_dispatch). A push touching anything else does not redeploy it.
  • The Pages project is Direct Upload (created by wrangler pages project create) — no Git-connected auto-build.
  • Gated by Cloudflare Access (email policy); page is noindex.

One-time setup (manual — account-gated)

  1. CF dashboard → My Profile → API Tokens → create a token with Pages: Edit (and Account → read). Add it to GitHub repo → Settings → Secrets → Actions as CLOUDFLARE_API_TOKEN.
  2. Add CLOUDFLARE_ACCOUNT_ID (CF dashboard → account ID) as a second secret.
  3. Trigger the workflow (push a brand change, or run it via Actions → Deploy brand site → Run workflow). First run creates the namaste-ji-brand Pages project and deploys.
  4. Zero Trust → Access → Applications → add the email policy to the *.pages.dev domain.

Do not connect the Pages project to Git in the dashboard — CI is the only deployer.

Implemented — the console (back office)

Three workflows realize the BACK-OFFICE.md §11 split (staging automatic, prod manual/gated):

  • deploy-console.ymlstaging, on merge to main (paths apps/console/**, services/console-api/**, packages/**) + workflow_dispatch. Builds the UI same-origin (no VITE_API_TARGET → relative /api) to the namaste-ji-console-staging Pages project, and the Worker via wrangler deploy --env staging. The UI serves bo-staging.namasteji.org/*, the Worker the /api/* route — one origin, no CORS, whole host behind Cloudflare Access.
  • deploy-console-prod.ymlproduction, workflow_dispatch only, gated by a confirm input + the production GitHub Environment (set required reviewers under repo Settings → Environments). Deploys the prod Worker + namaste-ji-console Pages project.
  • infra.yml — Alchemy resource provisioning (workflow_dispatch).

Runtime notes:

  • CI CLOUDFLARE_API_TOKEN needs Workers + Pages + Secrets Store scope (the staging Worker has a secrets_store_secrets binding). Set GitHub secrets via gh secret set NAME --body "$VALUE" — a value piped over stdin can carry a trailing newline → wrangler 6111 Invalid Authorization header.
  • Console deploys run on Node 22 (wrangler v4 requires Node ≥22).
  • SSL (free plan): Universal SSL covers only root + first-level subdomains, so BO hosts stay first-level (bo-staging, bo); a second-level host can’t get a free edge cert.
  • Secrets: Langfuse keys live in Cloudflare Secrets Store (BO §7), bound via secrets_store_secrets — no per-worker secret injection in CI.

Decisions

#DecisionRationale
CI-1CI-as-deployer (GitHub Actions + Wrangler); Pages on Direct UploadUniform, path-scoped control across Pages + Workers; no every-push rebuilds
CI-2Path/affected-scoped deploys (paths: now → Turbo --affected later)A push only deploys the targets it actually changed
CI-3Per-target deploy units; PR previews + prod on main; env protectionIndependent, safe, reviewable deploys
CI-4Alchemy IaC owns CF resources + bindings per environmentReproducible infra, no click-ops
CI-5Console: merge→staging auto-deploy; prod is manual + GitHub-Environment-gatedMatches BO §11; staging stays current, prod ships deliberately
CI-6BO hosts are first-level subdomains (bo-staging, bo)Free Universal SSL covers root + one level only; second-level needs paid ACM

Open

  • Adopt Turborepo + workspaces when the first JS/TS app lands; move the brand site to apps/brand/ and update the path filter.
  • Turbo remote cache backend (CF R2/Workers vs Turbo’s hosted).
  • Migrate the brand Pages-project creation from the wrangler bootstrap step to Alchemy.
  • Add PR preview deploys (branch deploys) per target once previews are wanted.