CI/CD Deployment¶
Purpose¶
Both deployment paths in full - what actually happens, in what order, and the exact gaps between them.
Verifying whether the README's "manual deployment" claim still holds¶
It no longer needs to be verified against a stale claim - README.md has been corrected. It
previously said: "The deployment workflow is available under
.github/workflows/deploy-cloudflare.yml and is intentionally manual until Cloudflare repository
secrets are configured." Reading .github/workflows/ci.yml directly shows a second,
automatic job:
deploy-production:
needs: validate
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
This runs on every push to main that passes validate - no manual trigger, no approval gate.
docs/DEPLOYMENT.md (the pre-existing deployment runbook) already documented this automatic
pipeline in full detail and did not repeat the README's "manual" framing. As of
fix/platform-hardening, README.md itself now describes this automatic pipeline accurately too.
Path 1: automatic (ci.yml's deploy-production)¶
Trigger: push to main (after validate passes).
Order (each step's failure stops everything after it - see Operations → Rollback for how to read a failure):
Apply D1 migrations -> Deploy API -> Deploy Corporate -> Deploy Software ->
Deploy Logistics -> Deploy Commerce -> Deploy Embroidery -> Deploy Admin ->
Production smoke test
Not included: Investors. See "Coverage gaps" below.
Concurrency: group: akshaya-production, cancel-in-progress: false - a second push while a
deploy is running queues behind it rather than cancelling or racing it.
Path 2: manual (deploy-cloudflare.yml, workflow_dispatch)¶
Trigger: Actions tab → "Deploy Cloudflare" → Run workflow (human-initiated only).
Two independent jobs:
build-and-deploy-web (matrix, fail-fast: false):
Corporate, Software, Logistics, Commerce, Embroidery, Admin, Investors
- each: npm install -> npm run build -w <workspace> -> wrangler deploy
deploy-api:
npm install
-> inject CLOUDFLARE_D1_DATABASE_ID into workers/api/wrangler.jsonc via sed
(see "The sed step is a no-op today" below)
-> wrangler d1 migrations apply --remote
-> wrangler deploy --config workers/api/wrangler.jsonc
Admin and Embroidery were added to this matrix in fix/platform-hardening, resolving the gap
that used to exist here (see "Coverage gaps" below for its prior state). Does not run the
production smoke test - run npm run smoke:production yourself afterward if you use this path.
Concurrency: same akshaya-production group as Path 1 - queues behind an in-flight automatic
deploy rather than racing it.
Coverage gaps (real, documented, not silently "fixed")¶
| App | Automatic (ci.yml) |
Manual (deploy-cloudflare.yml) |
|---|---|---|
| Corporate, Software, Logistics, Commerce, Embroidery, Admin | ✅ | ✅ |
| Investors | ❌ (out of scope / inactive) | ✅ (pre-existing, unchanged) |
Before fix/platform-hardening, Admin and Embroidery were missing from the manual path's matrix -
docs/DEPLOYMENT.md §3 flagged this explicitly and deliberately did not "fix" it by expanding the
matrix, since it might have been intentional. That gap has since been confirmed accidental and
resolved by adding both to the matrix. The one gap left standing is Investors, which is a
deliberate, scope-driven asymmetry (see Architecture → Applications
"Investors is the outlier"), not an oversight - its pre-existing manual-deploy entry was left as-is
rather than removed, since removing it would be a separate, deliberate change to existing
production deploy capability.
The sed step is a no-op today¶
deploy-cloudflare.yml's deploy-api job runs
sed -i "s/REPLACE_AFTER_D1_CREATE/${CLOUDFLARE_D1_DATABASE_ID}/g" workers/api/wrangler.jsonc
before deploying - but workers/api/wrangler.jsonc already has a real database_id committed and
no longer contains that placeholder anywhere. Confirmed by direct read. docs/DEPLOYMENT.md §2
already documents this precisely; left as-is here too, since deciding whether to remove the step
(and the CLOUDFLARE_D1_DATABASE_ID secret) is a deliberate call for whoever owns this workflow,
not a drive-by cleanup this portal should make.
Required secrets¶
See Secrets for the full GitHub-secret-vs-Worker-secret distinction.
| Secret | Used by |
|---|---|
CLOUDFLARE_API_TOKEN |
Both workflows, every wrangler call |
CLOUDFLARE_ACCOUNT_ID |
Both workflows, every wrangler call |
CLOUDFLARE_D1_DATABASE_ID |
deploy-cloudflare.yml's deploy-api only (see "no-op" above) |
Database / R2 requirements¶
D1 migrations are applied before any app/API deploy in both paths - by the time new code that
expects a new column/table is live, the schema change has already landed. No R2-specific
provisioning step exists in either workflow - the bucket is assumed to already exist (created
once via scripts/cloudflare-bootstrap.sh, see
Infrastructure → Cloudflare).
Expected outputs¶
A successful deploy-production run: 8 green steps (7 deploys + smoke test), each step's log
showing a successful wrangler deploy/migration apply. The smoke test step's own output lists
each of the 7 endpoints with [PASS]/[FAIL] and a reason per endpoint.
Rollback¶
See Operations → Rollback - no automated rollback exists, by
design; docs/DEPLOYMENT.md §5 has the full manual procedure.