Deploy
EstimateIQ deploys as a single Cloudflare Worker to esb.guru on the derekethandavis
Cloudflare account (d810128e…).
What a deploy ships
wrangler deploy (run from cloudflare/) publishes, in one artifact:
- the Hono API, the Astro SSR UI (built into the Worker), and the static Assets bundle;
- the Durable Objects (
EstimateIQ,SlaTimerDO), created by thev1migration on first deploy; - the
RevisionWorkflowand theMAIL_INGESTqueue consumer; - the hourly SLA cron (
0 * * * *) and the esb.guru custom domain.
cd cloudflarebunx wrangler deployBuild
The root bun run build runs the full pipeline that CI mirrors: seed → snapshot → build:web (Astro) → build:docs. The docs site (this site) builds separately into docs/dist and is copied to
web/dist/docs, so it ships alongside the app at https://esb.guru/docs.
GitHub Actions CI/CD
Two workflows run in the asdtransport/revsrc repo:
- ci (
.github/workflows/ci.yml) — on every push / PR: install,typecheck,bun run build, and awrangler deploy --dry-runbundle check to confirm the Worker compiles for the Workers runtime. - deploy (
.github/workflows/deploy.yml) — on push tomain(or manual dispatch): build, apply D1 migrations (wrangler d1 migrations apply estimateiq --remote), thenwrangler deploy.
Cloudflare credentials are repo-level: CLOUDFLARE_API_TOKEN (secret) and CLOUDFLARE_ACCOUNT_ID
(variable). Worker secrets (ANTHROPIC_API_KEY, CF_AIG_TOKEN, REVSRC_SECRET_KEY, …) persist on
the Worker across deploys — set once with wrangler secret put, never re-pushed by CI.
Access & environment
The ENVIRONMENT var gates Cloudflare Access: preview leaves the Access gate off so esb.guru
is reachable immediately; flip to production (with the Access app + ACCESS_AUD wired) to require a
valid Access JWT on every non-public path. /healthz is always public.
First-time deploy — full runbook
A fresh deployment comes up with an empty Durable Object and an empty search index — nothing is auto-seeded. These post-deploy steps are run by hand (or scripted); the app is up after step 3, but not populated until step 5, and search returns nothing until you reindex (step 6).
-
Provision the data plane (once per account/env) — Terraform stands up D1, KV, R2, Queues, Vectorize. Paste the output ids into
cloudflare/wrangler.jsonc(or import the existing ones).Terminal window cd infra/terraform && export TF_VAR_cloudflare_api_token=cfat_… && terraform applyterraform output bindings # ids → wrangler.jsonc -
Set the Worker secrets (once; they persist across deploys — CI never re-pushes them):
Terminal window cd cloudflareprintf '%s' "$ANTHROPIC_API_KEY" | wrangler secret put ANTHROPIC_API_KEYprintf '%s' "$CF_AIG_TOKEN" | wrangler secret put CF_AIG_TOKENopenssl rand -base64 32 | wrangler secret put REVSRC_SECRET_KEYprintf '%s' "$WEBHOOK_TOKEN" | wrangler secret put WEBHOOK_TOKEN -
Build & deploy the Worker (ships API + Astro SSR + Assets + DOs + Workflow + queue consumer + cron
- the
esb.gurucustom domain):
Terminal window bun run buildcd cloudflare && wrangler deploy - the
-
Apply D1 migrations (the event/audit mirror):
Terminal window wrangler d1 migrations apply estimateiq --remote -
Load the data through the API — the DO is empty until you do this:
Terminal window export REVSRC_ADMIN_TOKEN=<REVSRC_SECRET_KEY>bun run load # ansible → POST /api/admin/clear + /api/admin/load (infra/data/dataset.json) -
Build the search index — required, or semantic search returns 0 results. This calls Workers AI to embed every estimate into Vectorize:
Terminal window curl -X POST https://esb.guru/api/admin/reindex -H "authorization: Bearer $REVSRC_ADMIN_TOKEN"# → {"ok":true,"indexed":N}. Vectorize is eventually consistent — search is live within ~30s. -
Verify in Cloudflare — open the Worker in the dashboard and confirm the bindings (DO, D1, R2, KV, Vectorize, AI, Queue, Workflow, cron) are attached, then tail the live edge logs and check the trail:
Terminal window wrangler tail estimateiq # live requests hitting the Worker + DO (outcome: ok)curl -s https://esb.guru/healthz # { ok: true }curl -s https://esb.guru/api/metricsThe Audit log at
esb.guru/auditshould show theadmin.load+admin.clearentries you just created.