ci: deploy from main via Alchemy (CloudflareStateStore)#121

open2
main
ci/alchemy-deploy
openedJul 7, 2026byAAviddaram
View on GitHubTwo-way sync enabledPR last imported 19d ago

What changed

  • New .github/workflows/deploy.yml: on push to main (and manual dispatch), deploys all four apps in order — server, admin, storev2, agent — each via its own bun run deploy. Serialized with a deploy-production concurrency group (cancel-in-progress: false) so production deploys never overlap. 20-minute timeout, Bun 1.3.0.
  • apps/server, apps/admin, apps/storev2 alchemy.run.ts: pass a stateStore to the alchemy(...) call that, only when CI is set, uses CloudflareStateStore so all three apps share Alchemy state in Cloudflare (KV) instead of the local .alchemy filesystem. Locally (CI unset) behavior is unchanged (undefined → default local store).
  • apps/server/alchemy.run.ts: added adopt: true to the Hyperdrive("pscale-db", ...) resource so the first CI run adopts the existing Hyperdrive config instead of trying to recreate it.

GitHub secrets to create

  • CLOUDFLARE_API_TOKEN — Cloudflare API token with edit on: Workers Scripts, Workers KV Storage, Workers R2 Storage, Durable Objects, Hyperdrive, Workers Routes, DNS; plus Account Settings: Read.
  • CLOUDFLARE_ACCOUNT_ID — the target Cloudflare account ID.
  • ALCHEMY_STATE_TOKEN — a new, stable secret value used by CloudflareStateStore to authenticate to the shared state backend. Generate once and keep it stable across runs (rotating it orphans state).
  • ALCHEMY_PASSWORD — MUST equal the existing local ALCHEMY_PASSWORD used for current deploys. It encrypts/decrypts secret state; a different value cannot read existing state and will break adoption.
  • DOTENV_PROD — the full verbatim contents of the local .env.prod file (all prod env vars). The workflow writes this to .env.prod before install/deploy.

Required attended first run BEFORE merging

Do the first deploy locally with CI semantics, watching the log to confirm resources are ADOPTED, not recreated — especially the Hyperdrive (pscale-db) and the Durable-Object-backed workers (server's ProductSearchObject / TransferReconciliationObject). Recreating those would drop the DB binding / DO state.

1CI=1 \
2CLOUDFLARE_API_TOKEN=… \
3CLOUDFLARE_ACCOUNT_ID=… \
4ALCHEMY_STATE_TOKEN=… \
5ALCHEMY_PASSWORD=<existing local value> \
6bun run deploy

Run it per app (apps/server, apps/admin, apps/storev2, apps/agent) and verify each resource logs as adopted. Only merge once the attended run is clean.

Note

The --stage prod naming in each app's deploy script is load-bearing — it determines the physical resource names (e.g. server-api-prod, custom domains, WorkerRef({ service: "server-api-${stage}" }) in storev2). Do not change the stage name; it must stay prod for the shared state and cross-app references to resolve.


Open in Devin Review

Note

Deploy server, admin, storev2, and agent from main via GitHub Actions using CloudflareStateStore

  • Adds a deploy.yml GitHub Actions workflow that triggers on push to main and manual dispatch, running bun run deploy serially for apps/server, apps/admin, apps/storev2, and apps/agent.
  • Updates alchemy.run.ts, apps/admin/alchemy.run.ts, and apps/storev2/alchemy.run.ts to use CloudflareStateStore when process.env.CI is set, keeping local behavior unchanged.
  • Enables adopt: true on the pscale-db Hyperdrive resource in the server app.

Macroscope summarized 48e597f.

Checks

3 successful · 1 skipped
All checks passed
06ff3f0

Added 1 commit

  • ci: deploy all apps from main via alchemy with shared cloudflare stateaf60529
Synced from GitHub

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0128843b-5276-4b11-8aa9-00f8a5c30852

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ci/alchemy-deploy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Added 1 commit

  • ci: use existing ENV_PROD secret name06ff3f0

Blocker discovered after this PR was opened: apps/server now depends on khaan-client: github:darjss/khaan-client (committed on redesign/sorbet-pop, will reach main when #118 merges) — a private repo. GitHub Actions' built-in GITHUB_TOKEN cannot read other private repos, so bun install --frozen-lockfile in this workflow will fail once that dep lands on main. Before merging (or right after), pick one: (a) add a PAT with repo:read on darjss/khaan-client as a secret and wire it for bun's github: resolution, (b) SSH deploy key + git+ssh:// spec, or (c) publish khaan-client to a registry (npm private or GitHub Packages). Also note bun.lock has no khaan-client entry yet — owner needs one authed bun install locally to lock it.

Blocker resolved: khaan-client repo is now public (secret-scanned first), so github:darjss/khaan-client resolves authless in CI — no PAT/deploy-key/publish needed. bun.lock entry is recorded in the sorbet-pop working tree and lands with the next commit batch there. This PR is unblocked once v2 merges and the attended first deploy is done.